Skip to the content

ComponentsPanel

Page header

The top of a page: breadcrumb, title, description, meta and actions that fold into a menu when there is no room.

Import

import { PageHeader, PageHeaderBreadcrumb, PageHeaderHeading, PageHeaderTitle, PageHeaderMeta, PageHeaderDescription, PageHeaderActions, PageHeaderAction } from "@booleanpress/ui/page-header"

Usage

Every admin page starts with one. The title is the page's h1; the actions sit at the end of its line.

import { PlusIcon } from "lucide-react"
import {
  PageHeader,
  PageHeaderAction,
  PageHeaderActions,
  PageHeaderDescription,
  PageHeaderHeading,
  PageHeaderTitle,
} from "@booleanpress/ui/page-header"

export function MailersHeader() {
  return (
    <PageHeader>
      <PageHeaderHeading>
        <PageHeaderTitle>Mailers</PageHeaderTitle>
      </PageHeaderHeading>
      <PageHeaderDescription>The connections your sites send email through.</PageHeaderDescription>
      <PageHeaderActions>
        <PageHeaderAction onSelect={() => {}}>Export</PageHeaderAction>
        <PageHeaderAction pinned icon={<PlusIcon />} onSelect={() => {}}>Add mailer</PageHeaderAction>
      </PageHeaderActions>
    </PageHeader>
  )
}

PageHeaderBreadcrumb holds a Breadcrumb above the title. PageHeaderHeading is the title's line: PageHeaderTitle (an h1; as changes the level) and PageHeaderMeta for badges, avatars or a date beside it. PageHeaderDescription is the sentence under it.

PageHeaderActions takes PageHeaderActions, each a Button that runs onSelect. When the header is narrower than 36rem (its own width, not the window's), the actions that are not pinned fold into a "More actions" menu, and run the same onSelect from there; pinned keeps the page's main action a button; an icon-only action shows its aria-label as its text in the menu. collapse="always" or "never" decides once for all. The actions take at most 60% of the header's width and wrap past that, so a long title keeps its room. Put Tabs right after the header for the page's sections.

The examples on this page give the title as="h2", because the page already has its own h1; on a screen of your app, leave the default h1.

Examples

Basic

A title and a description.

import { PageHeader, PageHeaderDescription, PageHeaderHeading, PageHeaderTitle } from "@booleanpress/ui/page-header"

export default function PageHeaderBasic() {
  return (
    <PageHeader className="w-full">
      <PageHeaderHeading>
        <PageHeaderTitle as="h2">Email log</PageHeaderTitle>
      </PageHeaderHeading>
      <PageHeaderDescription>Every email your sites sent in the last 30 days.</PageHeaderDescription>
    </PageHeader>
  )
}

With breadcrumb

A breadcrumb above the title.

import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@booleanpress/ui/breadcrumb"
import {
  PageHeader,
  PageHeaderBreadcrumb,
  PageHeaderDescription,
  PageHeaderHeading,
  PageHeaderTitle,
} from "@booleanpress/ui/page-header"

export default function PageHeaderWithBreadcrumb() {
  return (
    <PageHeader className="w-full">
      <PageHeaderBreadcrumb>
        <Breadcrumb>
          <BreadcrumbList>
            <BreadcrumbItem>
              <BreadcrumbLink href="#">Settings</BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbSeparator />
            <BreadcrumbItem>
              <BreadcrumbLink href="#">Mailers</BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbSeparator />
            <BreadcrumbItem>
              <BreadcrumbPage>Primary SMTP</BreadcrumbPage>
            </BreadcrumbItem>
          </BreadcrumbList>
        </Breadcrumb>
      </PageHeaderBreadcrumb>
      <PageHeaderHeading>
        <PageHeaderTitle as="h2">Primary SMTP</PageHeaderTitle>
      </PageHeaderHeading>
      <PageHeaderDescription>smtp.example.com, port 587, STARTTLS.</PageHeaderDescription>
    </PageHeader>
  )
}

With actions

Two actions and a pinned main action at the end of the title.

import { DownloadIcon, PlusIcon, SendIcon } from "lucide-react"
import {
  PageHeader,
  PageHeaderAction,
  PageHeaderActions,
  PageHeaderDescription,
  PageHeaderHeading,
  PageHeaderTitle,
} from "@booleanpress/ui/page-header"

export default function PageHeaderWithActions() {
  return (
    <PageHeader className="w-full">
      <PageHeaderHeading>
        <PageHeaderTitle as="h2">Mailers</PageHeaderTitle>
      </PageHeaderHeading>
      <PageHeaderDescription>The connections your sites send email through.</PageHeaderDescription>
      <PageHeaderActions>
        <PageHeaderAction icon={<DownloadIcon />}>Export</PageHeaderAction>
        <PageHeaderAction icon={<SendIcon />}>Send test</PageHeaderAction>
        <PageHeaderAction pinned icon={<PlusIcon />}>
          Add mailer
        </PageHeaderAction>
      </PageHeaderActions>
    </PageHeader>
  )
}

With meta

A status badge, the assignees and the last update beside the title.

import { Avatar, AvatarFallback, AvatarGroup } from "@booleanpress/ui/avatar"
import { Badge } from "@booleanpress/ui/badge"
import {
  PageHeader,
  PageHeaderAction,
  PageHeaderActions,
  PageHeaderDescription,
  PageHeaderHeading,
  PageHeaderMeta,
  PageHeaderTitle,
} from "@booleanpress/ui/page-header"

export default function PageHeaderWithMeta() {
  return (
    <PageHeader className="w-full">
      <PageHeaderHeading>
        <PageHeaderTitle as="h2">Ticket #1041</PageHeaderTitle>
        <PageHeaderMeta>
          <Badge variant="warning">Waiting on customer</Badge>
          <AvatarGroup>
            <Avatar size="sm">
              <AvatarFallback>AR</AvatarFallback>
            </Avatar>
            <Avatar size="sm">
              <AvatarFallback>LW</AvatarFallback>
            </Avatar>
          </AvatarGroup>
          <span>Updated 4 Oct 2026</span>
        </PageHeaderMeta>
      </PageHeaderHeading>
      <PageHeaderDescription>Cannot connect to SES from the staging site.</PageHeaderDescription>
      <PageHeaderActions>
        <PageHeaderAction>Assign</PageHeaderAction>
        <PageHeaderAction pinned>Reply</PageHeaderAction>
      </PageHeaderActions>
    </PageHeader>
  )
}

Narrow

Under 36rem the actions fold into a “More actions” menu; the pinned Edit stays a button.

import { CopyIcon, PauseIcon, PencilIcon, Trash2Icon } from "lucide-react"
import {
  PageHeader,
  PageHeaderAction,
  PageHeaderActions,
  PageHeaderDescription,
  PageHeaderHeading,
  PageHeaderTitle,
} from "@booleanpress/ui/page-header"

export default function PageHeaderNarrow() {
  return (
    <PageHeader className="w-full max-w-sm rounded-md border p-4">
      <PageHeaderHeading>
        <PageHeaderTitle as="h2">Transactional SES</PageHeaderTitle>
      </PageHeaderHeading>
      <PageHeaderDescription>Under 36rem the actions fold into “More actions”.</PageHeaderDescription>
      <PageHeaderActions>
        <PageHeaderAction icon={<CopyIcon />}>Duplicate</PageHeaderAction>
        <PageHeaderAction icon={<PauseIcon />}>Pause</PageHeaderAction>
        <PageHeaderAction icon={<Trash2Icon />} variant="destructive">
          Delete
        </PageHeaderAction>
        <PageHeaderAction pinned icon={<PencilIcon />}>
          Edit
        </PageHeaderAction>
      </PageHeaderActions>
    </PageHeader>
  )
}

With tabs

Tabs right under the header for the page's sections.

import { PlusIcon } from "lucide-react"
import {
  PageHeader,
  PageHeaderAction,
  PageHeaderActions,
  PageHeaderHeading,
  PageHeaderTitle,
} from "@booleanpress/ui/page-header"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@booleanpress/ui/tabs"

export default function PageHeaderWithTabs() {
  return (
    <div className="flex w-full flex-col gap-4">
      <PageHeader>
        <PageHeaderHeading>
          <PageHeaderTitle as="h2">Customers</PageHeaderTitle>
        </PageHeaderHeading>
        <PageHeaderActions>
          <PageHeaderAction pinned icon={<PlusIcon />}>
            Add customer
          </PageHeaderAction>
        </PageHeaderActions>
      </PageHeader>
      <Tabs defaultValue="all">
        <TabsList>
          <TabsTrigger value="all">All</TabsTrigger>
          <TabsTrigger value="active">Active</TabsTrigger>
          <TabsTrigger value="archived">Archived</TabsTrigger>
        </TabsList>
        <TabsContent value="all" className="text-sm">2,418 customers.</TabsContent>
        <TabsContent value="active" className="text-sm">2,106 active customers.</TabsContent>
        <TabsContent value="archived" className="text-sm">312 archived customers.</TabsContent>
      </Tabs>
    </div>
  )
}

Accessibility

Semantics
A div with the title as a heading (h1 by default). The actions are buttons; the folded ones are a dropdown menu (role="menu") behind a button with aria-haspopup="menu".
Labels
The title is the page's main heading: one h1 per page, and as a lower level for a header inside a section. The menu button is named by the provider's moreActions string.
Focus
Nothing moves focus. The actions are in reading order, after the title; the menu returns focus to its button when it closes.
Known limits
  • Only PageHeaderActions fold into the menu; other elements in PageHeaderActions stay where they are.
  • The width where the actions fold is fixed at 36rem of the header's width.

Keyboard

Keyboard
KeyBehaviour
EnterorSpaceRuns the focused action, or opens the “More actions” menu.
↓or↑In the open menu, moves between the folded actions.
EscapeCloses the menu and returns focus to its button.

API

PageHeader

Renders a div and passes it every other prop.

PageHeaderBreadcrumb

Renders a div and passes it every other prop.

PageHeaderHeading

Renders a div and passes it every other prop.

PageHeaderTitle

Renders a h1 and passes it every other prop.

PageHeaderTitle props
PropTypeDefaultDescription
as"h2" | "h3" | "h1" | "h4" | "h5" | "h6"h1The heading level. h1 by default; a header inside a page section takes the level that fits the outline.

PageHeaderMeta

Renders a div and passes it every other prop.

PageHeaderDescription

Renders a p and passes it every other prop.

PageHeaderActions

Renders a div and passes it every other prop.

PageHeaderActions props
PropTypeDefaultDescription
collapse"auto" | "always" | "never"autoauto folds the actions into the menu when the header is narrow; always and never decide once for all.

PageHeaderAction

PageHeaderAction props
PropTypeDefaultDescription
iconReactNodeAn icon before the label, 14 px.
loadingbooleanShows the spinner in place of the leading icon, sets aria-busy and aria-disabled and ignores presses (a click, Enter, Space, a form's submission), while the button keeps its focus and its place in the tab order. With asChild the child (a link) gets the same.
pinnedbooleanfalseKeeps the action a button at every width, after the menu: the page's main action.
raisedboolean | null
roundedboolean | null
severity"success" | "info" | "warning" | "help" | "danger" | "contrast" | null
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | nullButton's size.
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | nullButton's look: outline by default, default when pinned. destructive shows red in the menu too.

Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.

Data attributes: data-slot="page-header" (PageHeader), data-slot="page-header-breadcrumb" (PageHeaderBreadcrumb), data-slot="page-header-heading" (PageHeaderHeading), data-slot="page-header-title" (PageHeaderTitle), data-slot="page-header-meta" (PageHeaderMeta), data-slot="page-header-description" (PageHeaderDescription), data-slot="page-header-actions" (PageHeaderActions), data-slot="page-header-action" (PageHeaderAction), and data-collapse.

Provider strings: moreActions (BooleanUIProvider's strings).

Theming

The title is --foreground, 20 px semibold; the description and meta --muted-foreground, 14 px. The actions are the library's Button and DropdownMenu.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--foregroundtext
--muted-foregroundtext