From 612c217c1f3de9ec558767a8676c285880499987 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Mar 2026 10:00:41 +0000 Subject: [PATCH] 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}