feat(ui): add HeaderMetadataUsers component and polish Header metadata/tags

- Add HeaderMetadataUsers component: single user shows avatar + name,
  multiple users show avatar stack with tooltip on hover
- Use Pressable from react-aria for tooltip trigger compatibility
- Switch tags and metadata text to body-medium variant
- Fix metadata item styling: secondary color label, no bold, no colon,
  flex row with gap-2 between label and value
- Update Header gap to space-3
- Update docs with HeaderMetadataUsers example, correct prop types,
  and synced default snippet

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
This commit is contained in:
Charles de Dreuille
2026-04-19 10:48:09 +02:00
parent fcc8c4d328
commit 0e8edce069
12 changed files with 319 additions and 57 deletions
@@ -1,6 +1,7 @@
'use client';
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
import { HeaderMetadataUsers } from '../../../../../packages/ui/src/components/Header/HeaderMetadataUsers';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import {
@@ -11,6 +12,16 @@ import {
import { MemoryRouter } from 'react-router-dom';
import { RiMore2Line } from '@remixicon/react';
const users = {
giles: {
name: 'Giles Peyton-Nicoll',
src: 'https://i.pravatar.cc/150?u=giles',
},
alice: { name: 'Alice Johnson', src: 'https://i.pravatar.cc/150?u=alice42' },
bob: { name: 'Bob Smith', src: 'https://i.pravatar.cc/150?u=bob' },
carol: { name: 'Carol Williams', src: 'https://i.pravatar.cc/150?u=carol' },
};
const tabs = [
{ id: 'overview', label: 'Overview', href: '/overview' },
{ id: 'checks', label: 'Checks', href: '/checks' },
@@ -35,10 +46,18 @@ const tags = [
{ label: 'Gold' },
];
const metadata = [
{ label: 'Owner', value: 'platform-team' },
const metadataUsers = [
{ label: 'Type', value: 'website' },
{ label: 'Tier', value: 'gold' },
{
label: 'Owner',
value: <HeaderMetadataUsers users={[users.giles]} />,
},
{
label: 'Contributors',
value: (
<HeaderMetadataUsers users={[users.alice, users.bob, users.carol]} />
),
},
];
export const WithEverything = () => (
@@ -47,8 +66,8 @@ export const WithEverything = () => (
title="Page Title"
tags={tags}
description="A short description of this page. Supports [inline links](https://backstage.io) and **bold text**."
metadata={metadataUsers}
tabs={tabs.slice(0, 2)}
breadcrumbs={breadcrumbs.slice(0, 2)}
customActions={
<>
<Button variant="secondary">Secondary</Button>
@@ -59,6 +78,12 @@ export const WithEverything = () => (
</MemoryRouter>
);
export const WithMetadataUsers = () => (
<MemoryRouter>
<Header title="Page Title" metadata={metadataUsers.slice(0, 2)} />
</MemoryRouter>
);
export const WithTags = () => (
<MemoryRouter>
<Header title="Page Title" tags={tags} />
@@ -76,7 +101,14 @@ export const WithDescription = () => (
export const WithMetadata = () => (
<MemoryRouter>
<Header title="Page Title" metadata={metadata} />
<Header
title="Page Title"
metadata={[
{ label: 'Owner', value: 'platform-team' },
{ label: 'Type', value: 'website' },
{ label: 'Tier', value: 'gold' },
]}
/>
</MemoryRouter>
);
+8 -8
View File
@@ -3,11 +3,11 @@ import { CodeBlock } from '@/components/CodeBlock';
import { Snippet } from '@/components/Snippet';
import {
WithEverything,
WithLongBreadcrumbs,
WithTabs,
WithTags,
WithDescription,
WithMetadata,
WithMetadataUsers,
WithCustomActions,
WithMenu,
} from './components';
@@ -19,7 +19,7 @@ import {
withTags,
withDescription,
withMetadata,
withBreadcrumbs,
withMetadataUsers,
withCustomActions,
withMenu,
} from './snippets';
@@ -63,6 +63,12 @@ Key-value pairs displayed below the description.
<Snippet open preview={<WithMetadata />} code={withMetadata} />
### Metadata with users
Use `HeaderMetadataUsers` as the metadata value to display users as avatars. A single user shows the avatar with their name beside it. Multiple users show a row of avatars — hover to reveal each name via tooltip.
<Snippet open preview={<WithMetadataUsers />} code={withMetadataUsers} />
### Tabs
Tabs auto-detect the active tab from the current route when `activeTabId` is omitted. Pass an explicit `activeTabId` to override, or `null` for no active tab.
@@ -79,12 +85,6 @@ Use `customActions` to add a dropdown menu.
<Snippet open preview={<WithMenu />} code={withMenu} />
### Breadcrumbs (deprecated)
The `breadcrumbs` prop is deprecated and will be removed in a future release. Labels are truncated at 240px.
<Snippet open preview={<WithLongBreadcrumbs />} code={withBreadcrumbs} />
<Theming definition={HeaderDefinition} />
<ChangelogComponent component={['header', 'header-page']} />
@@ -39,12 +39,13 @@ export const headerPagePropDefs: Record<string, PropDef> = {
label: {
type: 'string',
required: true,
description: 'The key label, displayed in bold.',
description: 'The key label, displayed in secondary color.',
},
value: {
type: 'ReactNode',
type: 'string | ReactNode',
required: true,
description: 'The value to display alongside the label.',
description:
'The value to display alongside the label. Pass a string for plain text or a ReactNode for custom content such as HeaderMetadataUsers.',
},
},
},
+47 -2
View File
@@ -2,7 +2,9 @@ export const usage = `import { Header } from '@backstage/ui';
<Header title="Page Title" />`;
export const defaultSnippet = `<Header
export const defaultSnippet = `import { Header, HeaderMetadataUsers } from '@backstage/ui';
<Header
title="Page Title"
tags={[
{ label: 'TypeScript' },
@@ -10,9 +12,28 @@ export const defaultSnippet = `<Header
{ label: 'Gold' },
]}
description="A short description. Supports [inline links](https://backstage.io) and **bold text**."
metadata={[
{ label: 'Type', value: 'website' },
{
label: 'Owner',
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...' }]} />,
},
{
label: 'Contributors',
value: (
<HeaderMetadataUsers
users={[
{ name: 'Alice Johnson', src: '...' },
{ name: 'Bob Smith', src: '...' },
{ name: 'Carol Williams', src: '...' },
]}
/>
),
},
]}
tabs={[
{ id: 'overview', label: 'Overview', href: '/overview' },
{ id: 'settings', label: 'Settings', href: '/settings' },
{ id: 'checks', label: 'Checks', href: '/checks' },
]}
customActions={
<>
@@ -79,3 +100,27 @@ export const withMetadata = `<Header
{ label: 'Tier', value: 'gold' },
]}
/>`;
export const withMetadataUsers = `import { Header, HeaderMetadataUsers } from '@backstage/ui';
<Header
title="Page Title"
metadata={[
{
label: 'Owner',
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...' }]} />,
},
{
label: 'Contributors',
value: (
<HeaderMetadataUsers
users={[
{ name: 'Alice Johnson', src: '...' },
{ name: 'Bob Smith', src: '...' },
{ name: 'Carol Williams', src: '...' },
]}
/>
),
},
]}
/>`;