Header
A sticky app header with a brand link, left / center / right areas, and a built-in mobile menu
that composes Modal, Slideover, or Drawer.
Its height is var(--ui-header-height),
shared with Main and Error.
Basic Usage
title renders a brand link,
children go to the center area, and the right snippet holds actions.
The center area hides below the lg breakpoint.
<script lang="ts">
import { Header, Button } from 'sv5ui';
</script>
<Header title="Acme" to="/">
<!-- children render in the center area (hidden below lg) -->
<nav class="flex items-center gap-6 text-sm font-medium">
<a href="/docs">Docs</a>
<a href="/pricing">Pricing</a>
<a href="/blog">Blog</a>
</nav>
{#snippet right()}
<Button label="Sign in" variant="ghost" size="sm" />
<Button label="Get started" size="sm" />
{/snippet}
</Header>Mobile Menu Modes
Below lg a toggle button appears
(default icon lucide:menu, new in the icons config).
Its menu is a real Modal, Slideover, or Drawer selected with mode; menu forwards typed options to that overlay.
This demo forces the toggle visible on desktop so you can try it.
<!-- Below the lg breakpoint the center area hides and a
toggle button appears. Its menu composes Modal (default),
Slideover, or Drawer via `mode`. -->
<Header title="Acme" mode="slideover" menu={{ side: 'left', overlay: true }}>
{#snippet body()}
<!-- Rendered inside the overlay -->
<nav class="flex flex-col gap-2">
<a href="/docs">Docs</a>
<a href="/pricing">Pricing</a>
<a href="/blog">Blog</a>
</nav>
{/snippet}
</Header>Customizing the Toggle
toggle accepts ButtonProps or false; toggleSide moves it to the left. toggleSlot replaces it entirely
while keeping the composed onclick and aria-expanded wiring.
<!-- The toggle is a Button: customize it with ButtonProps,
move it with toggleSide, or hide it with false. -->
<Header
title="Acme"
toggle={{ color: 'primary', variant: 'soft' }}
toggleSide="left"
>
{#snippet body()}...{/snippet}
</Header>
<!-- Replace it entirely: your markup receives a composed
onclick and the correct aria-expanded state. -->
<Header title="Acme">
{#snippet toggleSlot()}
<Button label="Menu" variant="outline" size="sm" trailingIcon="lucide:menu" />
{/snippet}
{#snippet body()}...{/snippet}
</Header>Open State & autoClose
Bind open to observe or drive the menu. autoClose (default true) dismisses the menu on route change,
which is what you want for navigation menus.
Menu is closed
<script lang="ts">
import { Header } from 'sv5ui';
let open = $state(false);
</script>
<!-- `open` is bindable. `autoClose` (default true) closes the
menu automatically when the route changes. -->
<Header title="Acme" bind:open autoClose={false}>
{#snippet body()}...{/snippet}
</Header>
<p>Menu is {open ? 'open' : 'closed'}</p>Layout Areas
Beyond the three main areas, top and bottom render full-width rows above and
below the header row, and titleSlot swaps
the brand link for your own markup (a logo, for example).
<Header>
{#snippet titleSlot()}
<!-- Replaces the default brand link -->
<img src="/logo.svg" alt="Acme" class="h-6" />
{/snippet}
{#snippet left()}
<!-- After the brand, before the center -->
<Badge label="Beta" color="warning" size="xs" />
{/snippet}
{#snippet top()}
<!-- Full-width row above the header row -->
<Banner title="v2.3 is out!" color="primary" />
{/snippet}
{#snippet bottom()}
<!-- Full-width row below the header row -->
<nav>...secondary nav...</nav>
{/snippet}
</Header>Header Height
The height is not a prop: it is the --ui-header-height CSS variable
(default 4rem) from sv5ui/theme.css.
Override it once and Header, Main, and Error stay aligned. See the Theming guide.
The demo scopes the variable to its frame; in your app you set it once on :root.
/* Header height comes from a CSS variable defined in
sv5ui/theme.css. Main and Error subtract the same
variable, so changing it keeps the shell aligned. */
:root {
--ui-header-height: 5rem;
}UI Slots
Use the `ui` prop to override classes on internal elements.
| Slot | Description |
|---|---|
root | Sticky `<header>` element. Height comes from `--ui-header-height` |
container | Inner Container-aligned flex row |
left | Left area holding the brand title and the `left` snippet |
center | Center area (the `children` snippet). Hidden below the `lg` breakpoint |
right | Right area holding the `right` snippet and the toggle |
title | Brand title link |
toggle | Mobile menu toggle button (hidden on `lg` and up) |
top | Full-width row above the header row |
bottom | Full-width row below the header row |
body | Mobile menu content wrapper inside the overlay |
Snippets
| Snippet | Description |
|---|---|
titleSlot | Replaces the default brand title link |
left | Extra content in the left area, after the brand title |
children | Center area content, typically navigation links. Hidden below `lg` |
right | Content in the right area, before the toggle button |
toggleSlot | Replaces the default toggle button. Receives a composed `onclick` and `aria-expanded` |
top | Content rendered above the main header row |
bottom | Content rendered below the main header row |
body | Mobile menu content rendered inside the overlay body |
content | Replaces the entire mobile menu layout |
Props
| Prop | Type | Default |
|---|---|---|
title | string | - |
to | string | '/' |
mode | 'modal' | 'slideover' | 'drawer' | 'modal' |
menu | HeaderMenuProps | - |
toggle | boolean | ButtonProps | true |
toggleSide | 'left' | 'right' | 'right' |
open | boolean | false |
autoClose | boolean | true |
as | keyof HTMLElementTagNameMap | 'header' |
ref | HTMLElement | null | null |
class | string | - |
ui | Record<Slot, Class> | - |