From 38bb056aa6cf263569799760d1ab817e14163aa1 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 29 Apr 2026 15:26:16 +0100 Subject: [PATCH 1/7] fix(ui): adjust plugin header spacing Align PluginHeader spacing across tabbed and non-tabbed variants so the component owns the surrounding page spacing more consistently. Signed-off-by: Charles de Dreuille --- .changeset/plugin-header-spacing.md | 7 ++ .storybook/themes/spotify.css | 11 +-- .../src/components/Header/Header.module.css | 4 - .../PluginHeader/PluginHeader.module.css | 13 ++- .../components/PluginHeader/PluginHeader.tsx | 87 +++++++++---------- .../recipes/PluginHeaderAndHeader.stories.tsx | 26 +++++- 6 files changed, 85 insertions(+), 63 deletions(-) create mode 100644 .changeset/plugin-header-spacing.md diff --git a/.changeset/plugin-header-spacing.md b/.changeset/plugin-header-spacing.md new file mode 100644 index 0000000000..bebf7a5608 --- /dev/null +++ b/.changeset/plugin-header-spacing.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Adjusted PluginHeader spacing so headers with and without tabs align more consistently with surrounding page content. + +**Affected components:** PluginHeader, Header diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index f91eb62e02..d22b1ef6aa 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -245,11 +245,12 @@ * this file for easier scanning alongside other component overrides above. */ [data-theme-name='spotify'] { + .bui-PluginHeader { + margin-top: var(--bui-space-4); + } + .bui-PluginHeaderToolbar { height: 32px; - border: none; - background: none; - margin-top: var(--bui-space-4); } .bui-PluginHeaderTabsWrapper { @@ -258,8 +259,4 @@ margin-left: 0; margin-top: var(--bui-space-2); } - - .bui-HeaderTop { - padding-top: var(--bui-space-4); - } } diff --git a/packages/ui/src/components/Header/Header.module.css b/packages/ui/src/components/Header/Header.module.css index 58bcf2699d..60db85cb25 100644 --- a/packages/ui/src/components/Header/Header.module.css +++ b/packages/ui/src/components/Header/Header.module.css @@ -32,10 +32,6 @@ box-sizing: border-box; } - .bui-HeaderTop { - padding-top: var(--bui-space-6); - } - .bui-HeaderStickySentinel { height: 1px; margin-bottom: -1px; diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.module.css b/packages/ui/src/components/PluginHeader/PluginHeader.module.css index 1ab0f9570a..41a31b78e4 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.module.css +++ b/packages/ui/src/components/PluginHeader/PluginHeader.module.css @@ -17,15 +17,23 @@ @layer tokens, base, components, utilities; @layer components { + .bui-PluginHeader { + margin-bottom: var(--bui-space-6); + padding-inline: var(--bui-space-5); + } + .bui-PluginHeaderToolbar { display: flex; flex-direction: row; align-items: center; justify-content: space-between; - padding-inline: var(--bui-space-5); color: var(--bui-fg-primary); height: 52px; - border-bottom: 1px solid var(--bui-border-1); + border-bottom: solid 1px var(--bui-border-1); + + &[data-has-tabs] { + border-bottom: none; + } } .bui-PluginHeaderToolbarContent { @@ -69,7 +77,6 @@ } .bui-PluginHeaderTabsWrapper { - padding-inline: var(--bui-space-3); border-bottom: 1px solid var(--bui-border-1); } } diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.tsx b/packages/ui/src/components/PluginHeader/PluginHeader.tsx index e410ce91f9..5d9b176907 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.tsx +++ b/packages/ui/src/components/PluginHeader/PluginHeader.tsx @@ -25,7 +25,6 @@ import { Box } from '../Box'; import { Link } from '../Link'; import { RiShapesLine } from '@remixicon/react'; import { Text } from '../Text'; -import { BgReset } from '../../hooks/useBg'; declare module 'react-aria-components' { interface RouterConfig { @@ -53,7 +52,7 @@ export const PluginHeader = (props: PluginHeaderProps) => { } = ownProps; const hasTabs = tabs && tabs.length > 0; - const headerRef = useRef(null); + const rootRef = useRef(null); const animationFrameRef = useRef(undefined); const lastAppliedHeightRef = useRef(undefined); @@ -62,7 +61,7 @@ export const PluginHeader = (props: PluginHeaderProps) => { }, [customActions]); useIsomorphicLayoutEffect(() => { - const el = headerRef.current; + const el = rootRef.current; if (!el) { return undefined; } @@ -125,50 +124,44 @@ export const PluginHeader = (props: PluginHeaderProps) => { const titleText = title || 'Your plugin'; return ( - -
- -
- -

- {titleLink ? ( - - {titleText} - - ) : ( - - {titleText} - - )} -

-
-
{actionChildren}
-
- {tabs && ( - - - - {tabs?.map(tab => ( - - {tab.label} - - ))} - - +
+
+
+ - )} -
-
+

+ {titleLink ? ( + + {titleText} + + ) : ( + + {titleText} + + )} +

+ +
{actionChildren}
+ + {tabs && ( +
+ + + {tabs?.map(tab => ( + + {tab.label} + + ))} + + +
+ )} + ); }; diff --git a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx index 4f74572ef5..b3bad07185 100644 --- a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx +++ b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx @@ -49,7 +49,7 @@ import { // --------------------------------------------------------------------------- const PageContent = () => ( - + @@ -59,7 +59,7 @@ const PageContent = () => ( ); const LongPageContent = () => ( - + @@ -104,6 +104,28 @@ export const NoHeader = meta.story({ ), }); +export const NoHeaderWithTabs = meta.story({ + decorators: [withLayout], + render: () => ( + <> + } + title="APIs" + tabs={[ + { id: 'overview', label: 'Overview', href: '/apis' }, + { + id: 'definitions', + label: 'Definitions', + href: '/apis/definitions', + }, + { id: 'consumers', label: 'Consumers', href: '/apis/consumers' }, + ]} + /> + + + ), +}); + export const SimpleHeader = meta.story({ decorators: [withLayout], render: () => ( From 93d1d40e647ca6216677c6513d3c1ea03c9f145e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 29 Apr 2026 15:42:37 +0100 Subject: [PATCH 2/7] fix(ui): refine plugin header spacing Signed-off-by: Charles de Dreuille --- .storybook/themes/spotify.css | 21 ------------------- .../PluginHeader/PluginHeader.module.css | 4 +++- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index d22b1ef6aa..7a34299817 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -239,24 +239,3 @@ --bui-ring: rgba(255, 255, 255, 0.2); } - -/* - * Plugin header (@backstage/ui) and story shell header — kept at the bottom of - * this file for easier scanning alongside other component overrides above. - */ -[data-theme-name='spotify'] { - .bui-PluginHeader { - margin-top: var(--bui-space-4); - } - - .bui-PluginHeaderToolbar { - height: 32px; - } - - .bui-PluginHeaderTabsWrapper { - border: none; - background: none; - margin-left: 0; - margin-top: var(--bui-space-2); - } -} diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.module.css b/packages/ui/src/components/PluginHeader/PluginHeader.module.css index 41a31b78e4..d222dae965 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.module.css +++ b/packages/ui/src/components/PluginHeader/PluginHeader.module.css @@ -20,6 +20,7 @@ .bui-PluginHeader { margin-bottom: var(--bui-space-6); padding-inline: var(--bui-space-5); + margin-top: var(--bui-space-4); } .bui-PluginHeaderToolbar { @@ -28,11 +29,12 @@ align-items: center; justify-content: space-between; color: var(--bui-fg-primary); - height: 52px; border-bottom: solid 1px var(--bui-border-1); + padding-bottom: var(--bui-space-4); &[data-has-tabs] { border-bottom: none; + padding-bottom: var(--bui-space-1); } } From a9f3d7189f99c9166c90f678f01f188562e77d83 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 29 Apr 2026 15:52:11 +0100 Subject: [PATCH 3/7] Update PluginHeader.tsx Signed-off-by: Charles de Dreuille --- packages/ui/src/components/PluginHeader/PluginHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.tsx b/packages/ui/src/components/PluginHeader/PluginHeader.tsx index 5d9b176907..6e94bbe159 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.tsx +++ b/packages/ui/src/components/PluginHeader/PluginHeader.tsx @@ -125,7 +125,7 @@ export const PluginHeader = (props: PluginHeaderProps) => { return (
-
+
{actionChildren}
- {tabs && ( + {hasTabs && (
From 021b36800f1f32da7f61ab72ac371c5903b9ab56 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 30 Apr 2026 09:33:18 +0100 Subject: [PATCH 4/7] fix(ui): collapse plugin header spacing before headers Signed-off-by: Charles de Dreuille --- .changeset/core-header-marker.md | 5 +++ .changeset/plugin-header-spacing.md | 2 +- .../src/layout/Header/Header.tsx | 6 +++- packages/ui/package.json | 1 + .../PluginHeader/PluginHeader.module.css | 17 +++++++-- .../recipes/PluginHeaderAndHeader.stories.tsx | 35 +++++++++++++++++++ yarn.lock | 1 + 7 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 .changeset/core-header-marker.md diff --git a/.changeset/core-header-marker.md b/.changeset/core-header-marker.md new file mode 100644 index 0000000000..6260c8d690 --- /dev/null +++ b/.changeset/core-header-marker.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added a stable DOM marker to the legacy Header so adjacent layout components can coordinate spacing without relying on generated class names. diff --git a/.changeset/plugin-header-spacing.md b/.changeset/plugin-header-spacing.md index bebf7a5608..a1c370ea19 100644 --- a/.changeset/plugin-header-spacing.md +++ b/.changeset/plugin-header-spacing.md @@ -2,6 +2,6 @@ '@backstage/ui': patch --- -Adjusted PluginHeader spacing so headers with and without tabs align more consistently with surrounding page content. +Adjusted PluginHeader spacing and borders so headers with and without tabs align more consistently with surrounding page content, including when paired with page headers. **Affected components:** PluginHeader, Header diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index f5facda641..3320ba782a 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -228,7 +228,11 @@ export function Header(props: PropsWithChildren) { return ( <> -
+
( + <> + } title="APIs" /> + + + + ), +}); + +export const CoreComponentsHeaderWithTabs = meta.story({ + decorators: [withLayout], + render: () => ( + <> + } + title="APIs" + tabs={[ + { id: 'overview', label: 'Overview', href: '/apis' }, + { + id: 'definitions', + label: 'Definitions', + href: '/apis/definitions', + }, + { id: 'consumers', label: 'Consumers', href: '/apis/consumers' }, + ]} + /> + + + + ), +}); + export const WithTabs = meta.story({ decorators: [withLayout], render: () => ( diff --git a/yarn.lock b/yarn.lock index 37c405d2f9..b11895427f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7949,6 +7949,7 @@ __metadata: resolution: "@backstage/ui@workspace:packages/ui" dependencies: "@backstage/cli": "workspace:^" + "@backstage/core-components": "workspace:^" "@backstage/version-bridge": "workspace:^" "@braintree/sanitize-url": "npm:^7.1.2" "@internationalized/date": "npm:^3.12.0" From 4955e51e0738a10916b70385ec8619bd3191fa65 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 30 Apr 2026 09:56:17 +0100 Subject: [PATCH 5/7] fix(ui): handle core page after plugin header Signed-off-by: Charles de Dreuille --- .changeset/core-header-marker.md | 2 +- .../core-components/src/layout/Page/Page.tsx | 7 ++++++- .../PluginHeader/PluginHeader.module.css | 5 ++++- .../recipes/PluginHeaderAndHeader.stories.tsx | 17 ++++++++++++----- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.changeset/core-header-marker.md b/.changeset/core-header-marker.md index 6260c8d690..a523c6f0b7 100644 --- a/.changeset/core-header-marker.md +++ b/.changeset/core-header-marker.md @@ -2,4 +2,4 @@ '@backstage/core-components': patch --- -Added a stable DOM marker to the legacy Header so adjacent layout components can coordinate spacing without relying on generated class names. +Added stable DOM markers to the legacy Page and Header so adjacent layout components can coordinate spacing without relying on generated class names. diff --git a/packages/core-components/src/layout/Page/Page.tsx b/packages/core-components/src/layout/Page/Page.tsx index 4bfeb90b94..4b01e19b4a 100644 --- a/packages/core-components/src/layout/Page/Page.tsx +++ b/packages/core-components/src/layout/Page/Page.tsx @@ -59,7 +59,12 @@ export function Page(props: Props) { page: baseTheme.getPageTheme({ themeId }), })} > -
{children}
+
+ {children} +
); } diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.module.css b/packages/ui/src/components/PluginHeader/PluginHeader.module.css index 8268d9cab0..07cf403768 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.module.css +++ b/packages/ui/src/components/PluginHeader/PluginHeader.module.css @@ -28,7 +28,10 @@ } .bui-PluginHeader:has(+ .bui-HeaderTop), - .bui-PluginHeader:has(+ [data-backstage-core-header]) { + .bui-PluginHeader:has(+ [data-backstage-core-header]), + .bui-PluginHeader:has( + + [data-backstage-core-page] [data-backstage-core-header] + ) { --bui-plugin-header-margin-bottom: 0; --bui-plugin-header-toolbar-border-bottom: none; --bui-plugin-header-tabs-border-bottom: none; diff --git a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx index fab2262f3b..533b67add1 100644 --- a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx +++ b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx @@ -15,7 +15,10 @@ */ import preview from '../../../../.storybook/preview'; -import { Header as CoreHeader } from '@backstage/core-components'; +import { + Header as CoreHeader, + Page as CorePage, +} from '@backstage/core-components'; import type { StoryFn } from '@storybook/react-vite'; import { MemoryRouter } from 'react-router-dom'; import { BUIProvider } from '../provider'; @@ -143,8 +146,10 @@ export const CoreComponentsHeader = meta.story({ render: () => ( <> } title="APIs" /> - - + + + + ), }); @@ -166,8 +171,10 @@ export const CoreComponentsHeaderWithTabs = meta.story({ { id: 'consumers', label: 'Consumers', href: '/apis/consumers' }, ]} /> - - + + + + ), }); From 36825fe290bd48fad942477127d1d522fd5d313f Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 30 Apr 2026 15:01:45 +0100 Subject: [PATCH 6/7] Update PluginHeaderAndHeader.stories.tsx Signed-off-by: Charles de Dreuille --- packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx index 533b67add1..66e659e28c 100644 --- a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx +++ b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx @@ -53,7 +53,7 @@ import { // --------------------------------------------------------------------------- const PageContent = () => ( - + From eecd8130dc3f7c30235619459121b1addd5151a5 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 4 May 2026 08:41:17 +0100 Subject: [PATCH 7/7] fix(ui): tune plugin header core spacing Signed-off-by: Charles de Dreuille --- .../PluginHeader/PluginHeader.module.css | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.module.css b/packages/ui/src/components/PluginHeader/PluginHeader.module.css index 07cf403768..31c827a06b 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.module.css +++ b/packages/ui/src/components/PluginHeader/PluginHeader.module.css @@ -21,10 +21,15 @@ --bui-plugin-header-margin-bottom: var(--bui-space-6); --bui-plugin-header-toolbar-border-bottom: solid 1px var(--bui-border-1); --bui-plugin-header-tabs-border-bottom: 1px solid var(--bui-border-1); + --bui-plugin-header-toolbar-padding-bottom: var(--bui-space-4); + --bui-plugin-header-toolbar-tabs-padding-bottom: var(--bui-space-1); + --bui-plugin-header-background-color: transparent; + --bui-plugin-header-padding-top: var(--bui-space-4); margin-bottom: var(--bui-plugin-header-margin-bottom); padding-inline: var(--bui-space-5); - margin-top: var(--bui-space-4); + padding-top: var(--bui-plugin-header-padding-top); + background-color: var(--bui-plugin-header-background-color); } .bui-PluginHeader:has(+ .bui-HeaderTop), @@ -35,6 +40,19 @@ --bui-plugin-header-margin-bottom: 0; --bui-plugin-header-toolbar-border-bottom: none; --bui-plugin-header-tabs-border-bottom: none; + --bui-plugin-header-toolbar-padding-bottom: var(--bui-space-2); + --bui-plugin-header-toolbar-tabs-padding-bottom: var(--bui-space-2); + --bui-plugin-header-background-color: var(--bui-bg-neutral-1); + --bui-plugin-header-padding-top: var(--bui-space-2); + } + + .bui-PluginHeader:has(+ [data-backstage-core-header]) + .bui-PluginHeaderToolbarIcon, + .bui-PluginHeader:has( + + [data-backstage-core-page] [data-backstage-core-header] + ) + .bui-PluginHeaderToolbarIcon { + background-color: var(--bui-bg-neutral-3); } .bui-PluginHeaderToolbar { @@ -44,11 +62,11 @@ justify-content: space-between; color: var(--bui-fg-primary); border-bottom: var(--bui-plugin-header-toolbar-border-bottom); - padding-bottom: var(--bui-space-4); + padding-bottom: var(--bui-plugin-header-toolbar-padding-bottom); &[data-has-tabs] { border-bottom: none; - padding-bottom: var(--bui-space-1); + padding-bottom: var(--bui-plugin-header-toolbar-tabs-padding-bottom); } } @@ -69,7 +87,7 @@ flex-shrink: 0; } - .bui-PluginHeaderToolbarIcon { + .bui-PluginHeader .bui-PluginHeaderToolbarIcon { display: flex; align-items: center; justify-content: center;