7.8 KiB
SiteHeader
Function
The chrome at the top of a marketing or documentation site: brand at one end,
navigation in the middle, actions at the other, and a drawer for the narrow
viewport. It pins to the top of its scroll host and knows it is pinned, so
the app can change its elevation or background on [data-stuck] without the
block owning any treatment.
Composition map
| Part | Composes | Key props |
|---|---|---|
| root | Sticky (F1.1) |
offset — passed through; skipped entirely when sticky={false} |
| measure | Container |
containerSize (default lg) |
| the bar | Group |
justify="space-between" · align="center" |
brand / nav / actions |
— (app snippets) | the app composes Link, NavigationMenu, Button… |
mobileNav + mobileTrigger |
Drawer |
direction="end", bind:open ↔ mobileOpen |
Landmark + headings: emits a single <header> and NO heading — a site
header is a landmark, not a section, and the page's h1 belongs to whatever
follows (usually a hero). If the app puts several navigations on the page,
name them: the block passes aria-label straight through to the <header>,
and the nav snippet's own NavigationMenu takes its own name.
Coordination
Position under the 2026-07-31 doctrine (architecture/blocks.md §«Coordination»).
Owns nothing of its own. mobileOpen is a bindable SEAM to the canon
Drawer, which owns the open state, the focus trap and the scroll lock. The
block forwards it so the app can drive the header from outside; forwarding is
not owning.
Decisions
2026-07-23 — reference floor (dossier §P1: Tailwind Plus «Headers 8 + Navbars 11 + Flyout 7» · Untitled UI · Flowbite):
- Adopted: the three-cluster bar (brand / nav / actions), the pinned state
as a visual axis, the mobile drawer, and the flyout navigation — the
dossier's explicit parity gap («sin flyout/dropdown de navegación no hay
paridad»). We do not re-implement it: the app passes a
NavigationMenu, which already ships the flyout, into thenavsnippet. - Discarded: the references' static HTML dumps of every arrangement. The bar is ONE layout; a different arrangement is the app's markup inside the snippets, not a variant prop.
- The announcement banner is NOT baked in: the dossier's sibling category
is served by the
bannerblock (F2.11) or by the canonBannerinchildren, which renders under the bar. One header, one function. stickyis a prop, not a variant: a header that does not pin still is this block; skipping theStickywrapper keeps the DOM honest (no wrapper that does nothing).
Demo
web/routes/blocks/site-header/ — el block a sangre en la página (su
position: sticky se pega al viewport de verdad; enmarcarlo en una caja con
scroll enseñaría un comportamiento que nadie vive), con control vivo de los
cinco props, y los anchos de dispositivo servidos desde preview/ como
documento propio. El mini-sitio vive en SiteHeaderSite.svelte y lo comparten
las dos superficies.
Gaps
| Gap | Disposition |
|---|---|
| Scroll-hide (header retreats when scrolling down) | canon candidate — it is behaviour with contract surface (direction detection), so it goes to sticky first, never into this block (admission rule) |
| Skip-link | shells own it — it belongs to the page shell (app-shell / docs-shell, F3/F4), which knows what the main region is; putting it here would emit a second one on every shell page |
| Search field in the bar | app-land — it is a Field/Command the app composes into actions; the block does not need to know |
| Sub-navigation row under the bar | out — children renders under the bar; a second row of links is markup, not new API |
| A NAVIGATING call-to-action that looks like a button | CLOSED in the canon 2026-07-23 — surfaced while composing this block. It is served by composition, not by a new prop: Button's child (asChild) form now hands over a content snippet, so <a href {...props}>{@render content()}</a> gets the full solid paint AND keeps the icon / label / spinner slots, and soma stops stamping type on an element it does not own. Button still has no href and Link still owns navigation — both signed decisions hold. See the eidos Button README §«CTA que navega» |
Found while composing
NavigationMenuemitted no landmark (2026-07-23): its morfo declaresdefaultElement: 'nav'for the Provider, but the soma component rendered a<div>— so the component that is supposed to BE the site's navigation had no<nav>at all, the exact a11y gap the dossier attributes to every reference. Fixed in the canon (soma/components/navigation-menu), not papered over here.