App Shell

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).

ACME. Beta
Secondary navigation row
<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.

height: 4rem

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.

SlotDescription
rootSticky `<header>` element. Height comes from `--ui-header-height`
containerInner Container-aligned flex row
leftLeft area holding the brand title and the `left` snippet
centerCenter area (the `children` snippet). Hidden below the `lg` breakpoint
rightRight area holding the `right` snippet and the toggle
titleBrand title link
toggleMobile menu toggle button (hidden on `lg` and up)
topFull-width row above the header row
bottomFull-width row below the header row
bodyMobile menu content wrapper inside the overlay

Snippets

SnippetDescription
titleSlotReplaces the default brand title link
leftExtra content in the left area, after the brand title
childrenCenter area content, typically navigation links. Hidden below `lg`
rightContent in the right area, before the toggle button
toggleSlotReplaces the default toggle button. Receives a composed `onclick` and `aria-expanded`
topContent rendered above the main header row
bottomContent rendered below the main header row
bodyMobile menu content rendered inside the overlay body
contentReplaces the entire mobile menu layout

Props

PropTypeDefault
titlestring-
tostring'/'
mode'modal' | 'slideover' | 'drawer''modal'
menuHeaderMenuProps-
toggleboolean | ButtonPropstrue
toggleSide'left' | 'right''right'
openbooleanfalse
autoClosebooleantrue
askeyof HTMLElementTagNameMap'header'
refHTMLElement | nullnull
classstring-
uiRecord<Slot, Class>-