Site Header
Supply brand, navigation, and optional actions once. Below the breakpoint, SiteHeader automatically puts the hamburger in the inline navigation's position beside the brand, with compact mobile spacing (mirrored in RTL). Optional actions stay at the trailing edge. It presents the same navigation in a full-height, scrollable drawer with a dark backdrop. It handles focus, Escape, outside press, link selection, and page scroll locking. Applications supply links and routing; no menu slot, duplicate links, IDs, or drawer wiring are needed. Before JavaScript, navigation remains available inline unless a custom native menu supplies its mobile navigation. Native custom menu controls remain usable before upgrade. Use TopBar for contextual application tools.
Compact header
Branding, navigation, and an action. On mobile, the header supplies the menu automatically.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-site-header/ui-site-header.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<ui-site-header>
<a
slot="brand"
href="#home"
><ui-icon
name="compass"
></ui-icon>Folio</a>
<a
slot="navigation"
href="#guide"
>Guide</a>
<a
slot="navigation"
href="#components"
>Components</a>
<ui-icon-button
slot="actions"
label="Search"
size="sm"
>
<ui-icon
name="search"
></ui-icon>
</ui-icon-button>
</ui-site-header><script setup>
import { UiIcon, UiIconButton, UiSiteHeader } from "@threadlabs/looma";
</script>
<template>
<UiSiteHeader>
<template #brand>
<a
href="#home"
><UiIcon
name="compass"
/>Folio</a>
</template>
<template #navigation>
<a
href="#guide"
>Guide</a><a
href="#components"
>Components</a>
</template>
<template #actions>
<UiIconButton
label="Search"
size="sm"
>
<UiIcon
name="search"
/>
</UiIconButton>
</template>
</UiSiteHeader>
</template>import { UiIcon, UiIconButton, UiSiteHeader } from "@threadlabs/looma";
export default function Example() {
return <UiSiteHeader
slots={{
brand: <a
href="#home"
><UiIcon
name="compass"
/>Folio</a>,
navigation: <><a
href="#guide"
>Guide</a><a
href="#components"
>Components</a></>,
actions: <UiIconButton
label="Search"
size="sm"
>
<UiIcon
name="search"
/>
</UiIconButton>,
}}
/>;
}/** @jsxImportSource preact */
import { UiIcon, UiIconButton, UiSiteHeader } from "@threadlabs/looma";
export default function Example() {
return <UiSiteHeader
slots={{
brand: <a
href="#home"
><UiIcon
name="compass"
/>Folio</a>,
navigation: <><a
href="#guide"
>Guide</a><a
href="#components"
>Components</a></>,
actions: <UiIconButton
label="Search"
size="sm"
>
<UiIcon
name="search"
/>
</UiIconButton>,
}}
/>;
}<script>
import { UiIcon, UiIconButton, UiSiteHeader } from "@threadlabs/looma";
</script>
{#snippet brand()}
<a
href="#home"
><UiIcon
name="compass"
/>Folio</a>
{/snippet}
{#snippet navigation()}
<a
href="#guide"
>Guide</a><a
href="#components"
>Components</a>
{/snippet}
{#snippet actions()}
<UiIconButton
label="Search"
size="sm"
>
<UiIcon
name="search"
/>
</UiIconButton>
{/snippet}
<UiSiteHeader
slots={{ brand, navigation, actions }}
/>import { Component } from "@angular/core";
import { UiIcon, UiIconButton, UiSiteHeader } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSiteHeader, UiIcon, UiIconButton],
template: `
<header
ui-site-header
>
<ng-container ngProjectAs="[slot='brand']">
<a
href="#home"
><span
ui-icon
name="compass"
></span>Folio</a>
</ng-container>
<ng-container ngProjectAs="[slot='navigation']">
<a
href="#guide"
>Guide</a><a
href="#components"
>Components</a>
</ng-container>
<ng-container ngProjectAs="[slot='actions']">
<button
ui-icon-button
[label]="'Search'"
[size]="'sm'"
>
<span
ui-icon
name="search"
></span>
</button>
</ng-container>
</header>
`,
})
export default class Example {}Site navigation
A full-width header keeps its logo visible, places desktop navigation beside it, and puts search and account controls at the trailing edge. Below 64rem, the hamburger takes the inline navigation's position beside the logo, using compact mobile spacing. It opens the same navigation in a full-height, scrollable drawer with a dark backdrop. No menu trigger, duplicate links, IDs, or event wiring are needed. Pass actions individually so the header reserves their adjustable hit areas.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-site-header/ui-site-header.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-popover/ui-popover.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-form-field/ui-form-field.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<ui-site-header>
<a
slot="brand"
href="#home"
aria-label="Folio home"
><ui-icon
name="compass"
></ui-icon>Folio</a>
<a
slot="navigation"
href="#guide"
aria-current="page"
>Guide</a>
<a
slot="navigation"
href="#components"
>Components</a>
<a
slot="navigation"
href="#journal"
>Journal</a>
<ui-icon-button
slot="actions"
id="site-header-search-trigger"
size="sm"
label="Search pages"
>
<ui-icon
name="search"
></ui-icon>
</ui-icon-button>
<ui-icon-button
slot="actions"
id="site-header-account-trigger"
size="sm"
label="Account"
>
<ui-icon
name="user-round"
></ui-icon>
</ui-icon-button>
</ui-site-header>
<ui-popover
for="site-header-search-trigger"
>
<ui-form-field>
<label
slot="label"
for="site-header-search"
>Search pages</label>
<ui-input
id="site-header-search"
type="search"
placeholder="Find a page"
></ui-input>
</ui-form-field>
</ui-popover>
<ui-popover
for="site-header-account-trigger"
>
<nav
aria-label="Account"
>
<ui-stack
gap="s"
><a
href="#profile"
>Your profile</a><a
href="#settings"
>Settings</a></ui-stack>
</nav>
</ui-popover><script setup>
import {
UiFormField,
UiIcon,
UiIconButton,
UiInput,
UiPopover,
UiSiteHeader,
UiStack,
} from "@threadlabs/looma";
</script>
<template>
<UiSiteHeader>
<template #brand>
<a
href="#home"
aria-label="Folio home"
><UiIcon
name="compass"
/>Folio</a>
</template>
<template #navigation>
<a
href="#guide"
aria-current="page"
>Guide</a><a
href="#components"
>Components</a><a
href="#journal"
>Journal</a>
</template>
<template #actions>
<UiIconButton
id="site-header-search-trigger"
size="sm"
label="Search pages"
>
<UiIcon
name="search"
/>
</UiIconButton>
<UiIconButton
id="site-header-account-trigger"
size="sm"
label="Account"
>
<UiIcon
name="user-round"
/>
</UiIconButton>
</template>
</UiSiteHeader>
<UiPopover
for="site-header-search-trigger"
>
<UiFormField>
<template #label>
<label
for="site-header-search"
>Search pages</label>
</template>
<UiInput
id="site-header-search"
type="search"
placeholder="Find a page"
/>
</UiFormField>
</UiPopover>
<UiPopover
for="site-header-account-trigger"
>
<nav
aria-label="Account"
>
<UiStack
gap="s"
><a
href="#profile"
>Your profile</a><a
href="#settings"
>Settings</a></UiStack>
</nav>
</UiPopover>
</template>import {
UiFormField,
UiIcon,
UiIconButton,
UiInput,
UiPopover,
UiSiteHeader,
UiStack,
} from "@threadlabs/looma";
export default function Example() {
return <>
<UiSiteHeader
slots={{
brand: <a
href="#home"
aria-label="Folio home"
><UiIcon
name="compass"
/>Folio</a>,
navigation: <><a
href="#guide"
aria-current="page"
>Guide</a><a
href="#components"
>Components</a><a
href="#journal"
>Journal</a></>,
actions: <>
<UiIconButton
id="site-header-search-trigger"
size="sm"
label="Search pages"
>
<UiIcon
name="search"
/>
</UiIconButton>
<UiIconButton
id="site-header-account-trigger"
size="sm"
label="Account"
>
<UiIcon
name="user-round"
/>
</UiIconButton>
</>,
}}
>{" "}{" "}{" "}{" "}{" "}{" "}{" "}</UiSiteHeader>
<UiPopover
htmlFor="site-header-search-trigger"
>
<UiFormField
slots={{
label: <label
htmlFor="site-header-search"
>Search pages</label>,
}}
>
<UiInput
id="site-header-search"
type="search"
placeholder="Find a page"
/>
</UiFormField>
</UiPopover>
<UiPopover
htmlFor="site-header-account-trigger"
>
<nav
aria-label="Account"
>
<UiStack
gap="s"
><a
href="#profile"
>Your profile</a><a
href="#settings"
>Settings</a></UiStack>
</nav>
</UiPopover>
</>;
}/** @jsxImportSource preact */
import {
UiFormField,
UiIcon,
UiIconButton,
UiInput,
UiPopover,
UiSiteHeader,
UiStack,
} from "@threadlabs/looma";
export default function Example() {
return <>
<UiSiteHeader
slots={{
brand: <a
href="#home"
aria-label="Folio home"
><UiIcon
name="compass"
/>Folio</a>,
navigation: <><a
href="#guide"
aria-current="page"
>Guide</a><a
href="#components"
>Components</a><a
href="#journal"
>Journal</a></>,
actions: <>
<UiIconButton
id="site-header-search-trigger"
size="sm"
label="Search pages"
>
<UiIcon
name="search"
/>
</UiIconButton>
<UiIconButton
id="site-header-account-trigger"
size="sm"
label="Account"
>
<UiIcon
name="user-round"
/>
</UiIconButton>
</>,
}}
>{" "}{" "}{" "}{" "}{" "}{" "}{" "}</UiSiteHeader>
<UiPopover
htmlFor="site-header-search-trigger"
>
<UiFormField
slots={{
label: <label
htmlFor="site-header-search"
>Search pages</label>,
}}
>
<UiInput
id="site-header-search"
type="search"
placeholder="Find a page"
/>
</UiFormField>
</UiPopover>
<UiPopover
htmlFor="site-header-account-trigger"
>
<nav
aria-label="Account"
>
<UiStack
gap="s"
><a
href="#profile"
>Your profile</a><a
href="#settings"
>Settings</a></UiStack>
</nav>
</UiPopover>
</>;
}<script>
import {
UiFormField,
UiIcon,
UiIconButton,
UiInput,
UiPopover,
UiSiteHeader,
UiStack,
} from "@threadlabs/looma";
</script>
{#snippet brand()}
<a
href="#home"
aria-label="Folio home"
><UiIcon
name="compass"
/>Folio</a>
{/snippet}
{#snippet navigation()}
<a
href="#guide"
aria-current="page"
>Guide</a><a
href="#components"
>Components</a><a
href="#journal"
>Journal</a>
{/snippet}
{#snippet actions()}
<UiIconButton
id="site-header-search-trigger"
size="sm"
label="Search pages"
>
<UiIcon
name="search"
/>
</UiIconButton>
<UiIconButton
id="site-header-account-trigger"
size="sm"
label="Account"
>
<UiIcon
name="user-round"
/>
</UiIconButton>
{/snippet}
{#snippet label()}
<label
for="site-header-search"
>Search pages</label>
{/snippet}
<UiSiteHeader
slots={{ brand, navigation, actions }}
> </UiSiteHeader>
<UiPopover
for="site-header-search-trigger"
>
<UiFormField
slots={{ label }}
>
<UiInput
id="site-header-search"
type="search"
placeholder="Find a page"
/>
</UiFormField>
</UiPopover>
<UiPopover
for="site-header-account-trigger"
>
<nav
aria-label="Account"
>
<UiStack
gap="s"
><a
href="#profile"
>Your profile</a><a
href="#settings"
>Settings</a></UiStack>
</nav>
</UiPopover>import { Component } from "@angular/core";
import {
UiFormField,
UiIcon,
UiIconButton,
UiInput,
UiPopover,
UiSiteHeader,
UiStack,
} from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSiteHeader, UiIcon, UiIconButton, UiPopover, UiFormField, UiInput, UiStack],
template: `
<header
ui-site-header
>
<ng-container ngProjectAs="[slot='brand']">
<a
href="#home"
aria-label="Folio home"
><span
ui-icon
name="compass"
></span>Folio</a>
</ng-container>
<ng-container ngProjectAs="[slot='navigation']">
<a
href="#guide"
aria-current="page"
>Guide</a><a
href="#components"
>Components</a><a
href="#journal"
>Journal</a>
</ng-container>
<ng-container ngProjectAs="[slot='actions']">
<button
ui-icon-button
id="site-header-search-trigger"
[size]="'sm'"
[label]="'Search pages'"
>
<span
ui-icon
name="search"
></span>
</button>
<button
ui-icon-button
id="site-header-account-trigger"
[size]="'sm'"
[label]="'Account'"
>
<span
ui-icon
name="user-round"
></span>
</button>
</ng-container>
</header>
<span
ui-popover
[for]="'site-header-search-trigger'"
>
<div
ui-form-field
>
<ng-container ngProjectAs="[slot='label']">
<label
for="site-header-search"
>Search pages</label>
</ng-container>
<input
ui-input
id="site-header-search"
[type]="'search'"
placeholder="Find a page"
>
</div>
</span>
<span
ui-popover
[for]="'site-header-account-trigger'"
>
<nav
aria-label="Account"
>
<div
ui-stack
[gap]="'s'"
><a
href="#profile"
>Your profile</a><a
href="#settings"
>Settings</a></div>
</nav>
</span>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: header
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
breakpoint | The viewport width at which navigation changes from an automatic mobile drawer to inline links: sm is 40rem, md 48rem, and lg 64rem. | breakpoint | keyword | lg | sm, md, lg |
close-label | Accessible name of the drawer's close button. | closeLabel | string | Close navigation | — |
label | Accessible name for the primary navigation region. | label | string | Main | — |
menu-label | Accessible name of the automatic mobile menu button. | menuLabel | string | Open navigation | — |
sticky | Keeps the header at the top of its scrolling container. | sticky | boolean | false | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
breakpoint | "sm" | "md" | "lg" | lg | sm, md, lg |
closeLabel | string | Close navigation | — |
label | string | Main | — |
menuLabel | string | Open navigation | — |
sticky | boolean | false | — |
Events
No events.
Slots
| Name | Purpose |
|---|---|
brand | Named content for the brand region. |
menu | Named content for the menu region. |
navigation | Named content for the navigation region. |
actions | Named content for the actions region. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-site-header-border | — | var(--ui-border) |
--ui-site-header-compact-height | — | 3.75rem |
--ui-site-header-height | — | 4.25rem |
--ui-site-header-navigation-width | — | 280px |
--ui-site-header-padding-inline | — | var(--ui-space-4) |
--ui-site-header-surface | — | var(--ui-surface) |
--ui-site-header-z-index | — | 10 |
Shared tokens
| Token | Fallback |
|---|---|
--ui-border | — |
--ui-border-divider | — |
--ui-border-width | — |
--ui-control-min-block-size | — |
--ui-elevation-overlay | var(--ui-shadow-lg) |
--ui-focus-ring | — |
--ui-focus-width | — |
--ui-font-bold | — |
--ui-font-normal | — |
--ui-font-semibold | — |
--ui-font-size-lg | — |
--ui-font-size-sm | — |
--ui-line-height-tight | — |
--ui-link-color | — |
--ui-motion-reveal | — |
--ui-motion-reveal-ease | — |
--ui-overlay-backdrop | rgb(0 0 0 / 0.32) |
--ui-overlay-border | var(--ui-border-divider) |
--ui-overlay-surface | var(--ui-surface-elevated) |
--ui-scroll-fade-size | 24px |
--ui-shadow-lg | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-space-4 | — |
--ui-space-5 | — |
--ui-space-6 | — |
--ui-surface | — |
--ui-surface-elevated | — |
--ui-text-primary | — |