Header Nav
A batteries-included top app bar composed from the chrome bricks (esa-app-bar + esa-icon-link + esa-nav-dropdown): brand slot, data-driven primary nav with dropdowns, an actions slot, and a user menu. Disclosures are native details/summary, so it needs zero runtime JS. For full control, compose the bricks directly.
Preview
Open Reports or the avatar to expand the native disclosures.
<EsaHeaderNav items={items} user={user}>
<strong slot="brand">ESA</strong>
</EsaHeaderNav> States
Brand only, no signed-in user — the avatar / user menu is omitted when user is absent.
<EsaHeaderNav items={items}>
<strong slot="brand">Beacon</strong>
</EsaHeaderNav> API
| Prop | Type | Default | Description |
|---|---|---|---|
items | EsaHeaderNavItem[] | [] | Primary nav links. Each is { label; href?; icon?; children? }; an item with children renders a dropdown. |
user | EsaHeaderNavUser | — | Optional signed-in user { name; email?; avatarUrl?; initials? }. When present, renders the avatar + user menu. |
Slots
| Prop | Type | Default | Description |
|---|---|---|---|
brand | slot | — | Left-aligned logo / wordmark area. |
actions | slot | — | Right-aligned controls placed before the user menu (e.g. search, notifications). |
Nav dropdowns and the user menu are native details/summary disclosures (via esa-nav-dropdown) — no JavaScript required.
Accessibility
- Each item with children renders an
esa-nav-dropdowndisclosure; plain items render anesa-icon-link. - Disclosures are native details/summary — keyboard toggling and the expanded state come for free.
- The avatar button carries an
aria-labelnaming the user (User menu for …). - Decorative chevrons and glyph SVGs are
aria-hidden.
Theming surface
Every public token this component reads, extracted from its source at build time.
The Resolves to column walks each token's real lineage —
component → semantic → primitive → raw value — so you
can see exactly where re-pointing it lands. A spoke re-skins by overriding these in
its theme-<slug>.css; component internals are never edited. No
hook for what you need? File it with /request-lego.
| Token | Tier | Resolves to |
|---|---|---|
--focus-ring-color | component | #65ba74 via --color-border-focus → --color-grass-8 |
--focus-ring-width | component | 2px |
--header-nav-avatar-bg | component | #46a758 via --color-primary → --color-grass-9 |
--header-nav-avatar-size | component | 32px |
--header-nav-avatar-text | component | #fcfcfc via --color-text-inverse → --color-gray-1 |
--header-nav-border-color | component | #cecece via --color-border → --color-gray-7 |
--header-nav-menu-item-color | component | #646464 via --color-text-secondary → --color-gray-11 |
--color-border | semantic | #cecece via --color-gray-7 |
--color-danger-strong | semantic | #ce2c31 via --color-red-11 |
--color-surface | semantic | #fcfcfc via --color-gray-1 |
--color-surface-sunken | semantic | #f0f0f0 via --color-gray-3 |
--color-text-muted | semantic | #838383 via --color-gray-10 |
--color-text-primary | semantic | #202020 via --color-gray-12 |
--color-text-secondary | semantic | #646464 via --color-gray-11 |
--focus-ring-offset | semantic | 2px |
--type-size-100 | semantic | clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) |
--type-size-200 | semantic | clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) |
--font-weight-semibold | primitive | 550 |
--radius-100 | primitive | 0.25rem |
--radius-200 | primitive | 0.5rem |
--radius-full | primitive | 9999px |
--shadow-200 | primitive | 0 4px 20px -4px rgba(0, 0, 0, 0.06) |
--spacing-100 | primitive | 0.25rem |
--spacing-150 | primitive | 0.375rem |
--spacing-200 | primitive | 0.5rem |
--spacing-300 | primitive | 0.75rem |
--spacing-400 | primitive | 1rem |
--spacing-500 | primitive | 1.5rem |
--z-dropdown | primitive | 50 |