Page header
Composable page-level header. Title + subtitle + breadcrumbs + actions + tabs + metadata.
PageHeader is the standard page-top header — composes Breadcrumb, Button, TabBar, and a metadata grid into one component. Inherits background from parent context (no surface of its own), so it works on pages with any background.
Use it for
- Detail page headers (company / project / record pages)
- Section headers that need breadcrumbs + page title + primary action
- Dashboards with metadata + tab navigation
- Any page where the top region groups identity + navigation + primary CTA
For Sheet headers (inside a <Sheet>), the Sheet component composes its own — don't reach for PageHeader inside a Sheet body.
Import
import { PageHeader } from '@brikdesigns/bds';Anatomy
PageHeader has six optional regions plus a required title. From top to bottom:
breadcrumbs— Breadcrumb above the title rowmedia+title— identity mark (left) + page H1subtitle— supporting paragraph under the titleactions— right-aligned action buttons (typically a Button or ButtonGroup)metadata— built-in key/value grid (Owner, Status, Updated)tabs— TabBar for in-page section nav
Variants
Title only
<PageHeader title="Dashboard" />Title + subtitle
<PageHeader
title="My account"
subtitle="Manage your membership plan and billing."
/>With breadcrumbs and actions
<PageHeader
title="Birdwell & Mutlak"
breadcrumbs={
<Breadcrumb items={[
{ label: 'Clients', href: '/clients' },
{ label: 'Birdwell & Mutlak' },
]} />
}
actions={<Button variant="primary">Edit</Button>}
/>With service tag
ServiceTag renders to the left of the title for service-line pages.
<PageHeader
title="Website Design"
subtitle="Custom web development and design."
media={<ServiceTag category="marketing" variant="icon" serviceName="Custom Web Development" size="lg" />}
actions={<Button variant="primary">Edit Service</Button>}
/>With metadata
metadata is a built-in key/value grid for page-level facts.
<PageHeader
title="Brand Refresh"
metadata={[
{ label: 'Owner', value: 'Sarah Chen' },
{ label: 'Status', value: <Badge status="positive">Active</Badge> },
{ label: 'Updated', value: '2 days ago' },
]}
/>With tabs
<PageHeader
title="My Account"
subtitle="Manage your membership plan."
tabs={
<TabBar items={[
{ label: 'Overview', active: true },
{ label: 'Billing' },
{ label: 'Security' },
]} />
}
/>Sizes
size controls the title scale. Default lg.
The step names are offset from the tokens they render. lg is the page-title default, so it renders --heading-xl — the Section-headline step a page's <h1> gets. Only md and sm name-match their token.
size | Title token |
|---|---|
lg (default) | --heading-xl |
md | --heading-md |
sm | --heading-sm |
<PageHeader title="Compact dashboard header" size="sm" />
<PageHeader title="Standard page header" size="md" />
<PageHeader title="Hero detail page" size="lg" />Read and edit mode
PageHeader can drive a page-level read↔edit toggle. In mode="read" it renders an Edit button wired to onEdit; in mode="edit" it swaps to Save / Cancel wired to onSave / onCancel, with saveLoading / saveDisabled for in-flight commits. Labels are overridable via editLabel / saveLabel / cancelLabel. Omit mode for a static header. (An explicit actions prop overrides these mode-driven buttons.)
<PageHeader
title="Acme Corporation"
mode={mode}
onEdit={() => setMode('edit')}
onSave={handleSave}
onCancel={() => setMode('read')}
saveLoading={saving}
/>The example above is a client component — onEdit is a function, so it cannot cross the RSC boundary. On a server page, pass editHref instead: it renders the same Edit button as a navigating <a>, so the page stays a React Server Component with no 'use client' and no function prop.
// Server component — no 'use client'
<PageHeader title="Acme Corporation" mode="read" editHref={`/admin/contacts/${id}/edit`} />editHref wins if both it and onEdit are passed. Edit-mode onSave / onCancel are inherently client; a server page that needs them wraps its own client component.
Structured actions
actions accepts any ReactNode, but a bare flex row lets each surface pick its own ordering and mix button sizes. PageHeaderActions fixes the hierarchy once:
- Ordering —
destructive(far left) ·secondary·primary(far right). The primary lands at the reading-end; the destructive is held away from it. - Spacing + alignment — composed on
ButtonGroup(align="end"), the canonical action-row treatment. - Size — a single
size(defaultmd) is injected into every slotted button that doesn't set its own, so a group can't render asmnext to anlg.
Slots accept rendered elements — a raw Button or a consumer wrapper (DeleteCompanyButton) — so it fits both catalog buttons and app-specific action components. Non-breaking: pass a PageHeaderActions into actions only when you want the enforced hierarchy; existing raw-ReactNode usage is unchanged.
<PageHeader
title="Acme Corp"
actions={
<PageHeaderActions
destructive={<Button variant="negative">Delete</Button>}
secondary={<Button variant="outline">Edit</Button>}
primary={<Button variant="primary">New Proposal</Button>}
/>
}
/>Pattern: full company detail page
The canonical layout — breadcrumb + identity mark + title + actions + metadata + tabs.
<PageHeader
title="Birdwell & Mutlak"
subtitle="Active client · Dental"
breadcrumbs={
<Breadcrumb items={[
{ label: 'Clients', href: '/clients' },
{ label: 'Birdwell & Mutlak' },
]} />
}
media={<ServiceTag category="dental" variant="icon" size="lg" />}
actions={<ButtonGroup>
<Button variant="ghost">Archive</Button>
<Button variant="primary">Edit</Button>
</ButtonGroup>}
metadata={[
{ label: 'Owner', value: 'Nick Stanerson' },
{ label: 'Industry', value: 'Dental' },
{ label: 'Updated', value: '3 days ago' },
]}
tabs={
<TabBar items={[
{ label: 'Brand', active: true },
{ label: 'Services' },
{ label: 'Locations' },
{ label: 'Activity' },
]} />
}
/>When not to use
- Don't use PageHeader inside a Sheet. Use Sheet's built-in header instead — different shape, different semantics.
- Don't use PageHeader without a
title. It's required and serves as the page H1; without it, the page outline breaks. - Don't put complex JSX in
metadatavalues. Metadata is for short pinned facts. For richer per-section content, use DataSection.
Accessibility
- Renders a real
<header>landmark. - The title is the page's
<h1>— only one PageHeader per page. - Breadcrumb, TabBar, and action elements keep their own semantics.
API
| Prop | Type | Default |
|---|---|---|
title | string (required) | — |
subtitle | string | — |
media | ReactNode | — |
badge | ReactNode | deprecated — alias for media (#1705) |
breadcrumbs | ReactNode | — |
actions | ReactNode | — |
tabs | ReactNode | — |
metadata | MetadataItem[] | — |
size | 'sm' | 'md' | 'lg' | 'lg' |
sticky | boolean | false |
mode | 'read' | 'edit' | — |
onEdit | () => void | — |
editHref | string | — |
onSave | () => void | — |
onCancel | () => void | — |
saveLoading | boolean | — |
saveDisabled | boolean | — |
editLabel | string | 'Edit' |
saveLabel | string | 'Save' |
cancelLabel | string | 'Cancel' |
MetadataItem
interface MetadataItem {
label: string;
value: ReactNode;
}Related
- Breadcrumb — common breadcrumbs prop content
- TabBar — common tabs prop content
- ServiceTag — common
mediaprop content (variant="icon") - DataSection — for body sections below the header
- Sheet — has its own header for in-Sheet contexts
- Storybook playground