From 612c217c1f3de9ec558767a8676c285880499987 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Mar 2026 10:00:41 +0000 Subject: [PATCH 1/2] fix(ui): open external Table row hrefs in a new tab Automatically apply `target="_blank"` and `rel="noopener noreferrer"` on `Row` when `href` is an external link, so external links in default BUI Table rows open in a new tab instead of navigating the current page. `rel` tokens are merged rather than replaced, so consumer-provided tokens (e.g. `rel="nofollow"`) are preserved while `noopener noreferrer` is always enforced whenever `target="_blank"` is in effect. Signed-off-by: Charles de Dreuille --- .changeset/fix-table-row-external-href.md | 7 ++++++ .../src/components/Table/components/Row.tsx | 23 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-table-row-external-href.md diff --git a/.changeset/fix-table-row-external-href.md b/.changeset/fix-table-row-external-href.md new file mode 100644 index 0000000000..c3e5b1f494 --- /dev/null +++ b/.changeset/fix-table-row-external-href.md @@ -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 diff --git a/packages/ui/src/components/Table/components/Row.tsx b/packages/ui/src/components/Table/components/Row.tsx index 3da46b3eaa..34b38c784e 100644 --- a/packages/ui/src/components/Table/components/Row.tsx +++ b/packages/ui/src/components/Table/components/Row.tsx @@ -36,9 +36,26 @@ export function Row(props: RowProps) { 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(props: RowProps) { {content} From 0733b76b757ee9ad82ff2dae0c368d9ec20c0922 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Mar 2026 10:44:22 +0000 Subject: [PATCH 2/2] Update Row.tsx Signed-off-by: Charles de Dreuille --- packages/ui/src/components/Table/components/Row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Table/components/Row.tsx b/packages/ui/src/components/Table/components/Row.tsx index 34b38c784e..75b715f7f7 100644 --- a/packages/ui/src/components/Table/components/Row.tsx +++ b/packages/ui/src/components/Table/components/Row.tsx @@ -91,7 +91,7 @@ export function Row(props: RowProps) { {...restProps} target={effectiveTarget} rel={effectiveRel} - className={classes.root} + className={clsx(classes.root, restProps.className)} data-react-aria-pressable={hasInternalHref ? 'true' : undefined} onAction={handlePress} >