# 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"`
- **Page:** <https://ui.booleanpress.com/components/page-header> · @booleanpress/ui 0.2.0

## Usage

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

```tsx
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](/components/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 `PageHeaderAction`s, 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](/components/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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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](/components/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 `PageHeaderAction`s 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

| Key | Behaviour |
| --- | --- |
| Enter or Space | Runs the focused action, or opens the “More actions” menu. |
| ↓ or ↑ | In the open menu, moves between the folded actions. |
| Escape | Closes 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.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `as` | `"h2" \| "h3" \| "h1" \| "h4" \| "h5" \| "h6"` | `h1` | The 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.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `collapse` | `"auto" \| "always" \| "never"` | `auto` | `auto` folds the actions into the menu when the header is narrow; `always` and `never` decide once for all. |

### PageHeaderAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `icon` | `ReactNode` |  | An icon before the label, 14 px. |
| `loading` | `boolean` |  | Shows 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. |
| `pinned` | `boolean` | `false` | Keeps the action a button at every width, after the menu: the page's main action. |
| `raised` | `boolean \| null` |  |  |
| `rounded` | `boolean \| null` |  |  |
| `severity` | `"success" \| "info" \| "warning" \| "help" \| "danger" \| "contrast" \| null` |  |  |
| `size` | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg" \| null` |  | Button's size. |
| `variant` | `"link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| null` |  | Button'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.

| Token | Used for |
| --- | --- |
| `--foreground` | text |
| `--muted-foreground` | text |
