Page Header
The top line of a page, with its title, an optional hint, page actions and a row of tabs.
@zenoxai/ui/patterns/page-header MarkdownAlerts
<PageHeader
className="w-full"
title="Alerts"
icon={<ShieldAlertIcon />}
hint="Every alert raised by your detection rules in the last 90 days."
actions={
<>
<Button variant="outline">
<DownloadIcon data-icon="inline-start" />
Export
</Button>
<Button>
<PlusIcon data-icon="inline-start" />
New rule
</Button>
</>
}
/>Usage
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@zenoxai/ui/components/button"
import { PageHeader } from "@zenoxai/ui/patterns/page-header"
export function AlertsHeader() {
return (
<PageHeader
title="Alerts"
icon={<ShieldAlertIcon />}
hint="Every alert raised by your detection rules in the last 90 days."
actions={<Button>New rule</Button>}
/>
)
}
Every page starts with one PageHeader. It renders the page’s only <h1>, at the page step of
the type scale (text-xl, semibold), so titles read the same on every screen. Do not build a
page title out of a plain heading and a flex row.
iconis the module’s lucide icon, the same one the sidebar shows. Pass it bare; the header sizes it to 20px.hintis one sentence that explains the page. It opens in a Help Tooltip next to the title and is never rendered as a paragraph, so the header stays one line tall. Leave it out when the title says enough.actionsholds the page’s filters and its primary action, right-aligned. Keep at most onedefaultbutton here; the rest areoutline.tabsis a row under the title, usually aTabsListwithvariant="line".
Long titles truncate. Keep them to the name of the page; put the record name, counts and dates in the page body.
PageHeader renders only the header line. The band around it (padding, the bottom border, the
background, a fixed height) belongs to the app shell, which knows whether the page scrolls under
the header or with it. Do not add padding or a border to the header itself.
Examples
With tabs
When the page has views of the same records, pass a line TabsList to tabs and wrap the
header and the panels in one Tabs. The line variant draws its own bottom rule, so the header
needs no border of its own.
Alerts
<Tabs defaultValue="open" className="w-full">
<PageHeader
title="Alerts"
icon={<ShieldAlertIcon />}
hint="Every alert raised by your detection rules in the last 90 days."
actions={
<Button>
<PlusIcon data-icon="inline-start" />
New rule
</Button>
}
tabs={
<TabsList variant="line">
<TabsTrigger value="open">Open</TabsTrigger>
<TabsTrigger value="assigned">Assigned to me</TabsTrigger>
<TabsTrigger value="resolved">Resolved</TabsTrigger>
</TabsList>
}
/>
<TabsContent value="open" className="pt-2 text-text-secondary">
42 open alerts across 6 tenants.
</TabsContent>
<TabsContent value="assigned" className="pt-2 text-text-secondary">
7 alerts assigned to you.
</TabsContent>
<TabsContent value="resolved" className="pt-2 text-text-secondary">
318 alerts resolved in the last 30 days.
</TabsContent>
</Tabs>Title only
Settings and detail pages often need only the title and the icon.
Assets
function PageHeaderTitleOnly() {
return <PageHeader className="w-full" title="Assets" icon={<ServerIcon />} />
}Inside the app shell
The shell draws the band: here, horizontal padding, a bottom border and the surface background. The header inside it is the same component as in the other examples.
Vulnerabilities
<div className="w-full overflow-hidden rounded-md border border-border-subtle">
{/* The band belongs to the app shell: padding, border and background. */}
<div className="border-b border-border-subtle bg-surface px-6 py-4">
<PageHeader
title="Vulnerabilities"
icon={<ShieldAlertIcon />}
actions={<Button variant="outline">Export</Button>}
/>
</div>
<div className="bg-background px-6 py-8 text-text-secondary">
Page content scrolls under the band.
</div>
</div>Accessibility
- The title is an
<h1>. Render onePageHeaderper page, and start the sections below it at<h2>. - The icon is decorative: lucide icons render with
aria-hidden. - The hint is a focusable button next to the
<h1>, not inside it, so the heading’s accessible name stays the title alone. The button’s name is the hint text: keep it to one short sentence. - Name icon-only buttons in
actionswitharia-label.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
PageHeader
PageHeader is the top of every page: one <h1> at the page step of the type scale (text-xl, semibold), an optional icon and hint, an action slot on the right, and an optional tab row underneath.
| Prop | Type | Default |
|---|---|---|
actions | ReactNodeRight-hand slot: filters, a primary action, or both. | - |
hint | stringOne-sentence description of the page, shown in an info tooltip next to the title. Never rendered as a paragraph: the header stays one line tall. | - |
icon | ReactNodeLeading icon, usually the module's lucide icon at size-5. | - |
tabs | ReactNodeRow under the title, typically a line-variant <TabsList>. | - |
title* | ReactNodeThe page title, rendered as the page's single <h1>. | - |
titleClassName | stringExtra classes for the <h1>, merged last so they win. | - |