# Page Header

The top line of a page, with its title, an optional hint, page actions and a row of tabs.

```tsx
import { DownloadIcon, PlusIcon, ShieldAlertIcon } from "lucide-react"
import { Button } from "@zenoxai/ui/components/button"
import { PageHeader } from "@zenoxai/ui/patterns/page-header"

function PageHeaderDemo() {
  return (
    <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

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

- `icon` is the module's lucide icon, the same one the sidebar shows. Pass it bare; the header
  sizes it to 20px.
- `hint` is one sentence that explains the page. It opens in a
  [Help Tooltip](/patterns/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.
- `actions` holds the page's filters and its primary action, right-aligned. Keep at most one
  `default` button here; the rest are `outline`.
- `tabs` is a row under the title, usually a `TabsList` with `variant="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.

```tsx
import { PlusIcon, ShieldAlertIcon } from "lucide-react"
import { Button } from "@zenoxai/ui/components/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@zenoxai/ui/components/tabs"
import { PageHeader } from "@zenoxai/ui/patterns/page-header"

function PageHeaderWithTabs() {
  return (
    <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.

```tsx
import { ServerIcon } from "lucide-react"
import { PageHeader } from "@zenoxai/ui/patterns/page-header"

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.

```tsx
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@zenoxai/ui/components/button"
import { PageHeader } from "@zenoxai/ui/patterns/page-header"

function PageHeaderInShell() {
  return (
    <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 one `PageHeader` per 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 `actions` with `aria-label`.

## API reference

`import { ... } from "@zenoxai/ui/patterns/page-header"`

### PageHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| actions | `ReactNode` |  | Right-hand slot: filters, a primary action, or both. |
| hint | `string` |  | One-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 | `ReactNode` |  | Leading icon, usually the module's lucide icon at size-5. |
| tabs | `ReactNode` |  | Row under the title, typically a line-variant <TabsList>. |
| title (required) | `ReactNode` |  | The page title, rendered as the page's single <h1>. |
| titleClassName | `string` |  | Extra classes for the <h1>, merged last so they win. |

