Merge pull request #33353 from backstage/charlesdedreuille/bacui-272-fix-table-row-external-href

fix(ui): open external Table row hrefs in a new tab
This commit is contained in:
Charles de Dreuille
2026-03-16 10:16:18 +00:00
committed by GitHub
2 changed files with 29 additions and 3 deletions
@@ -0,0 +1,7 @@
---
'@backstage/ui': patch
---
Fixed `Table` rows with external `href` values to open in a new tab by automatically applying `target="_blank"` and `rel="noopener noreferrer"`.
**Affected components**: Table
@@ -36,9 +36,26 @@ export function Row<T extends object>(props: RowProps<T>) {
props,
);
const { classes, columns, children, href } = ownProps;
const hasInternalHref = !!href && !isExternalLink(href);
const isExternal = isExternalLink(href);
const hasInternalHref = !!href && !isExternal;
const hasExternalHref = !!href && isExternal;
const hasInteraction = !!restProps.onAction || !!href;
// Derive the effective target, defaulting to _blank for external links.
const effectiveTarget = hasExternalHref ? '_blank' : restProps.target;
// Always include noopener noreferrer when target=_blank, merging any
// consumer-provided rel tokens to avoid reverse-tabnabbing risk.
const effectiveRel =
effectiveTarget === '_blank'
? [
...new Set([
'noopener',
'noreferrer',
...(restProps.rel?.split(/\s+/).filter(Boolean) ?? []),
]),
].join(' ')
: restProps.rel;
const handlePress = hasInteraction
? () => {
restProps.onAction?.();
@@ -71,9 +88,11 @@ export function Row<T extends object>(props: RowProps<T>) {
<InternalLinkProvider href={href}>
<ReactAriaRow
href={href}
className={classes.root}
data-react-aria-pressable={hasInternalHref ? 'true' : undefined}
{...restProps}
target={effectiveTarget}
rel={effectiveRel}
className={clsx(classes.root, restProps.className)}
data-react-aria-pressable={hasInternalHref ? 'true' : undefined}
onAction={handlePress}
>
{content}