Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Page Header

The standardized header at the top of a page — back button, breadcrumb trail, title, description, actions, secondary menu, and a favorite star.

Status
stable
Since
0.1.0
Accessibility pattern
banner landmark

Last updated

Spaces

Organize work into shared spaces.

Install

Add Page Header from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/page-header

The same command installs the registry items it composes: @vegastack/button, @vegastack/icon-button, @vegastack/truncated-text.

Usage

import { PageHeader } from "@/components/ui/page-header";
import { Button } from "@/components/ui/button";

<PageHeader
  title="Spaces"
  description="Organize work into shared spaces."
  actions={<Button>New space</Button>}
/>;

PageHeader is presentational — it owns layout, not behavior. You pass navigation (backHref / onBack), the breadcrumb node, right-side actions and secondaryMenu slots, and a favorite config; the host owns routing and persistence.

The registry component is client-rendered because it includes the optional favorite toggle and imperative onBack button path. Prefer backHref for URL-backed navigation; use onBack only for app-local behavior such as closing a picker or returning to a non-URL state.

Anatomy

PageHeader lays out, top to bottom, an optional breadcrumb row, then a title row split into a left title block (back button, <h1>, favorite star, description) and a right actions block (actions + secondary menu), followed by any children (tabs, search bars). Every region exposes a data-slot for styling and testing:

FavoriteStar — data-slot="page-header-favorite"
PageHeader — data-slot="page-header" | "page-header-actions" | "page-header-back" | "page-header-breadcrumb" | "page-header-description" | "page-header-title"
<PageHeader
  backHref="/settings" // ← page-header-back   (<a>, ChevronLeft)
  breadcrumb={<Breadcrumb>…</Breadcrumb>} // ← page-header-breadcrumb
  title="API Keys" // ← page-header-title  (<h1>)
  description="Manage keys." // ← page-header-description
  favorite={{ defaultActive: true }} // ← page-header-favorite (Star toggle)
  actions={<Button>Create key</Button>} // ← page-header-actions
  secondaryMenu={<DropdownMenu>…</DropdownMenu>}
>
  {/* children render below the header row */}
</PageHeader>
  • page-header — the root container (<header>, vertical stack). A banner landmark when PageHeader sits at the top of the page — not nested inside <article> / <aside> / <main> / <nav> / <section>.
  • page-header-breadcrumb — wraps the breadcrumb node above the title row. Omitted when no breadcrumb.
  • page-header-back — the back affordance (<a> with a ChevronLeft when backHref is set, a button when onBack is set). Omitted when neither is set.
  • page-header-title — the page <h1>. The title text itself routes through TruncatedText (data-slot="truncated-text", nested inside the <h1>), so an overlong tenant/workspace title clips with an ellipsis instead of overflowing the header row, and reveals in full via a tooltip on hover/focus. The <h1> stays the accessible heading — TruncatedText composes inside it, never replaces it.
  • page-header-favorite — the star toggle. Sets aria-pressed and data-active. Omitted when no favorite.
  • page-header-description — the muted description under the title. Omitted when no description.
  • page-header-actions — the right-aligned slot holding actions then secondaryMenu. Omitted when both are empty.

Examples

Minimal

The only required prop is title. With nothing else, PageHeader renders just the <h1> — every other slot (back button, breadcrumb, description, favorite, actions) is opt-in:

Profile

<PageHeader title="Profile" />

With breadcrumb, back button, and actions

A back button (rendered as a link via backHref), a full breadcrumb trail, a description, and two actions on the right:

API Keys

Manage keys for this workspace.

Back navigation

Pass backHref for URL-backed navigation (renders an <a>) or onBack for an app-specific imperative action (renders a <button>). The two render different elements; onBack takes precedence when both are set. The preview below uses onBack, so the back affordance is a real <button>:

Select plan

Choose a plan to continue. The back button closes the picker.

// Link — renders an <a>
<PageHeader title="Select plan" backHref="/billing" />

// Imperative app action — renders a <button>
<PageHeader title="Select plan" onBack={() => closePlanPicker()} />

Long title

An overlong tenant or workspace title truncates with an ellipsis instead of overflowing the header row. The full title reveals via a tooltip on hover or keyboard focus — a tap-to-toggle disclosure on devices that can't hover (see TruncatedText):

Q3 Platform Reliability & Performance Engineering Initiative Retrospective

An overlong tenant or workspace title truncates instead of overflowing, and reveals in full on hover or focus (tap on touch).

<PageHeader title="Q3 Platform Reliability & Performance Engineering Initiative Retrospective" />

Favorite star

The star is controlled when you pass active, or uncontrolled when you pass defaultActive. It reports the next state through onToggle. Pass disabled to remove it from the tab order.

The active star fills with neutral ink, not warning yellow. The status hues are rationed to actual status, and a favourite is a user's own mark rather than a caution — the fill is what carries the on/off read, so the colour was never doing that work.

The preview shows a controlled star (click to toggle) above a disabled one:

Q3 Roadmap

Controlled star — currently not starred.

Archived doc

Disabled star — non-interactive and out of the tab order.

// Uncontrolled — seeds initial state, host persists on toggle
<PageHeader title="Doc" favorite={{ defaultActive: true, onToggle: (next) => persist(next) }} />

// Controlled — host owns the state
<PageHeader title="Doc" favorite={{ active: isStarred, onToggle: setStarred }} />

// Disabled — non-interactive, out of the tab order
<PageHeader title="Doc" favorite={{ defaultActive: true, disabled: true }} />

Secondary menu

Compose your own overflow menu (e.g. a DropdownMenu with an IconButton trigger) and pass it via secondaryMenu — it renders to the right of actions:

Spaces

The overflow menu renders to the right of the actions.

<PageHeader
  title="Spaces"
  actions={<Button>New space</Button>}
  secondaryMenu={
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <IconButton aria-label="More actions">
            <MoreVertical />
          </IconButton>
        }
      />
      <DropdownMenuContent>{/* … */}</DropdownMenuContent>
    </DropdownMenu>
  }
/>

API Reference

PropTypeDefaultDescription
title*React.ReactNodeThe page title — rendered as the <h1>. Accepts a string or rich nodes. Truncated via TruncatedText when it overflows the title row, revealing the full title on hover/focus (tap on touch) — see the component doc.
actionsReact.ReactNodeRight-aligned action slot — typically one or more Buttons. Rendered on the title row, opposite the title block.
backHrefstringRenders a back button (ChevronLeft) before the title as a link to this href. Use for declarative navigation; prefer over onBack when you have a URL. Ignored when onBack is also set.
backLabelstring'Go back'Accessible name for the back button.
breadcrumbReact.ReactNodeOptional breadcrumb trail (or section name) rendered above the title row. Pass a Breadcrumb element or any node — the header is presentational and does not build the trail for you.
descriptionReact.ReactNodeOptional supporting copy rendered under the title (muted).
favoritePageHeaderFavoriteOptional favorite-star toggle rendered after the title. Omit to hide it.
onBack(() => void)Renders a back button (ChevronLeft) before the title that calls this handler. Use for app-local imperative behavior, such as closing a picker or returning to the previous in-app state. Prefer backHref for URL-backed navigation.
secondaryMenuReact.ReactNodeOptional overflow / secondary menu slot, rendered after actions on the right. Compose your own menu trigger (e.g. a DropdownMenu with an IconButton trigger) — kept as a slot so the header stays presentational.

Data attributes and CSS variables on PageHeader

AttributeValues
data-slot"page-header" | "page-header-actions" | "page-header-back" | "page-header-breadcrumb" | "page-header-description" | "page-header-title"

PageHeaderFavorite

PropTypeDefaultDescription
activebooleanControlled active (starred) state. When provided, the host owns the state and must update it from onToggle. Omit to run uncontrolled.
defaultActivebooleanfalseInitial active state when uncontrolled (no active prop).
disabledbooleanfalseDisables the toggle and removes it from the tab order.
labelstring'Favorite'Accessible name for the toggle. The current state is announced via aria-pressed, so pass the action label only.
onToggle((active: boolean) => void)Called with the next active state whenever the star is toggled.

FavoriteStar

FavoriteStar renders the controlled star toggle used by PageHeader's favorite slot. Prefer the favorite prop for ordinary page headers; export-level composition is available when the same toggle must be placed elsewhere.

Accessibility

  • The root renders <header>, a banner landmark when PageHeader sits at the top of the page (outside <article>/<aside>/<main>/<nav>/<section>), so assistive tech can jump straight to it.
  • The title renders as a single <h1> — one per page — so assistive tech announces the page's primary heading. Pass the breadcrumb as a labelled <nav aria-label="breadcrumb">.
  • An overlong title is keyboard-focusable when clipped (TruncatedText's overflow measurement), so the full title is reachable via Tab and a focus-triggered tooltip — not hover-only.
  • The back affordance is a real link (backHref) or button (onBack) with an accessible backLabel (default "Go back"), and shows a visible :focus-visible ring.
  • The favorite star is a toggle button: it carries aria-pressed reflecting the starred state and a label (default "Favorite"); the visual fill is decorative.
  • All interactive parts are keyboard reachable and operable; the visual order matches the DOM order.
KeyAction
TabMove focus through the back button, favorite star, and actions.
EnterFollow the back link, or activate the focused button.
SpaceActivate the focused button (back / favorite / action).
ContractStates tested
Behaviourdefault, active, disabled, pressed
Accessibilitydisabled, labeled, pressed, semantic-html
Visualdefault, hover, active

Do / Don't

Do
Keep one PageHeader (one h1) per page, use backHref for routing, and reserve onBack for app-local behavior.
Don't
Stack multiple PageHeaders or nest an h1 inside another heading — and don't hardcode navigation inside the header.

On this page