From c6fc76f53218c5bf54444c3a9e016b30cd7fa5c2 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Fri, 17 Apr 2026 13:43:54 +0200 Subject: [PATCH] fix(ui): prevent hidden Tabs tree from stomping active indicator opacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React Aria's `CollectionBuilder` renders `TabList`'s children into both a hidden collection-building tree and the real DOM. The hidden instance of `TabsIndicators` sits outside the `TabListStateContext` provider, so its `state` is `null` — causing its `updateCSSVariables` effect to hit the `else` branch and write `--active-tab-opacity: 0` to the `tabsRef` DOM element that the real instance also writes to. Under the right render ordering, this hidden write lands after the real instance's `opacity: 1` and makes the active indicator disappear on uncontrolled Tabs. Guard `updateCSSVariables` with an early return when `state == null` so the hidden instance never writes to the shared DOM element. Signed-off-by: Johan Persson --- .changeset/fix-tabs-active-indicator-disappearing.md | 7 +++++++ packages/ui/src/components/Tabs/TabsIndicators.tsx | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 .changeset/fix-tabs-active-indicator-disappearing.md diff --git a/.changeset/fix-tabs-active-indicator-disappearing.md b/.changeset/fix-tabs-active-indicator-disappearing.md new file mode 100644 index 0000000000..c8537940c9 --- /dev/null +++ b/.changeset/fix-tabs-active-indicator-disappearing.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed an issue where the active tab indicator would disappear shortly after page load for uncontrolled Tabs. + +**Affected components:** Tabs diff --git a/packages/ui/src/components/Tabs/TabsIndicators.tsx b/packages/ui/src/components/Tabs/TabsIndicators.tsx index aef792310e..400ecf82c2 100644 --- a/packages/ui/src/components/Tabs/TabsIndicators.tsx +++ b/packages/ui/src/components/Tabs/TabsIndicators.tsx @@ -32,6 +32,12 @@ export const TabsIndicators = (props: TabsIndicatorsProps) => { const prevSelectedKey = useRef(null); const updateCSSVariables = useCallback(() => { + // When rendered inside CollectionBuilder's hidden tree (for collection + // building), there is no TabListStateContext provider, so state is null. + // Bail out to avoid overwriting CSS variables on the shared tabsRef DOM + // element that the real instance also writes to. + if (state == null) return; + if (!tabsRef.current) return; const tabsRect = tabsRef.current.getBoundingClientRect();