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

NameDescriptionPropertyTypeDefaultAllowed values
breakpointThe viewport width at which navigation changes from an automatic mobile drawer to inline links: sm is 40rem, md 48rem, and lg 64rem.breakpointkeywordlgsm, md, lg
close-labelAccessible name of the drawer's close button.closeLabelstringClose navigation—
labelAccessible name for the primary navigation region.labelstringMain—
menu-labelAccessible name of the automatic mobile menu button.menuLabelstringOpen navigation—
stickyKeeps the header at the top of its scrolling container.stickybooleanfalse—

Framework props

NameTypeDefaultAllowed values
breakpoint"sm" | "md" | "lg"lgsm, md, lg
closeLabelstringClose navigation—
labelstringMain—
menuLabelstringOpen navigation—
stickybooleanfalse—

Events

No events.

Slots

NamePurpose
brandNamed content for the brand region.
menuNamed content for the menu region.
navigationNamed content for the navigation region.
actionsNamed 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

TokenDeclared valuesFallback
--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

TokenFallback
--ui-border—
--ui-border-divider—
--ui-border-width—
--ui-control-min-block-size—
--ui-elevation-overlayvar(--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-backdroprgb(0 0 0 / 0.32)
--ui-overlay-bordervar(--ui-border-divider)
--ui-overlay-surfacevar(--ui-surface-elevated)
--ui-scroll-fade-size24px
--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—