Brik Design System
Components

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:

  1. breadcrumbsBreadcrumb above the title row
  2. media + title — identity mark (left) + page H1
  3. subtitle — supporting paragraph under the title
  4. actions — right-aligned action buttons (typically a Button or ButtonGroup)
  5. metadata — built-in key/value grid (Owner, Status, Updated)
  6. tabsTabBar 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.

sizeTitle 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:

  • Orderingdestructive (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 (default md) is injected into every slotted button that doesn't set its own, so a group can't render a sm next to an lg.

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 metadata values. 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

PropTypeDefault
titlestring (required)
subtitlestring
mediaReactNode
badgeReactNodedeprecated — alias for media (#1705)
breadcrumbsReactNode
actionsReactNode
tabsReactNode
metadataMetadataItem[]
size'sm' | 'md' | 'lg''lg'
stickybooleanfalse
mode'read' | 'edit'
onEdit() => void
editHrefstring
onSave() => void
onCancel() => void
saveLoadingboolean
saveDisabledboolean
editLabelstring'Edit'
saveLabelstring'Save'
cancelLabelstring'Cancel'

MetadataItem

interface MetadataItem {
  label: string;
  value: ReactNode;
}

On this page

💬