Header
The site/app navigation header — LB.Header. Three base compositions from one component: contained bar (default, app-shell), mega-menu (categorized full-width panels, marketing), and floating pill (centered rounded island, landing pages). Logo, nav items, and CTA are slots. Responsive: nav collapses to an accessible modal drawer at ≤768px. Reuses .lb-btn for CTAs.
a11y (built in): mega-menus are disclosure widgets (button + aria-expanded, not role=menu — links stay links); Esc closes a panel/drawer and returns focus; the mobile drawer is a real modal (role=dialog + aria-modal + focus trap + scroll-lock); a visually-hidden skip-to-content link leads each header. Resize the window below 768px to see the drawer.
Usage: Load js/lb.js first, then js/components/lb-header.js; every <header data-lb-header> auto-inits, and a console warning names the missing script if you forget the module. You author both nav copies: the inline links in .lb-header__inner and the drawer content in [data-lb-header-drawer] are separate markup the component toggles, never clones — the burger is [data-lb-header-toggle], and a mega item is a .lb-header__item wrapping button[data-lb-header-mega] plus its sibling [data-lb-header-mega-panel]. The scroll listener attaches only when --transparent or --shrink is present (rAF-throttled) and toggles lb-header--scrolled — style against that class for custom scrolled looks. Observe lb-header-drawer-open/-close and lb-header-mega-open ({trigger}) / lb-header-mega-close, or call el._lbHeader methods directly.
A · Contained bar — default
Logo left, inline nav, CTAs right. The default and the app-shell header. Here shown --sticky inside a scroll box.
B · Mega-menu — marketing header
A nav item opens a full-width categorized panel. Click Resources. Disclosure pattern — Esc closes and returns focus; outside-click closes.
C · Floating pill — --floating
Centered rounded island hovering over content — the 2026 landing-page look. Solid by default (safe contrast). Glass/translucency is opt-in via --glass because translucent headers fail WCAG contrast over variable backgrounds — so it's never the default.
Scroll behaviors
--sticky— pins to the top of the scroll root (must be a direct child of it, never inside amin-height:100vhwrapper, or it stops sticking).--transparent— transparent over a hero, flips to solid once scrolled (JS adds--scrolledpast an 8px threshold; override withdata-lb-scroll-threshold).--shrink— trims vertical padding once scrolled.--glass— opt-in translucency + blur (only with the solid fallback baked in); never default.
API
Add data-lb-header to a <header>. Auto-inits. Instance at el._lbHeader: openDrawer() / closeDrawer() / toggleDrawer(), openMega(btn) / closeMega(). Events (bubbling): lb-header-drawer-open/-close, lb-header-mega-open/-close.
<header class="lb-header lb-header--sticky" data-lb-header>
<a class="lb-header__skip" href="#main">Skip to content</a>
<div class="lb-header__inner" data-lb-header-inner>
<a class="lb-header__brand" href="/"><span class="lb-header__logo">…logo…</span> Brand</a>
<nav class="lb-header__nav" aria-label="Primary">
<a class="lb-header__link" href="#">Product</a>
<!-- mega item -->
<div class="lb-header__item">
<button class="lb-header__link" data-lb-header-mega>Resources <span data-lb-icon="chevron-down"></span></button>
<div class="lb-header__mega" data-lb-header-mega-panel>…</div>
</div>
</nav>
<div class="lb-header__actions">
<a class="lb-btn lb-btn--primary lb-btn--medium" href="#">Get started</a>
</div>
<button class="lb-icon-btn lb-icon-btn--ghost lb-icon-btn--medium lb-header__toggle" data-lb-header-toggle aria-label="Open menu"><span data-lb-icon="menu"></span></button>
</div>
<div class="lb-header__drawer" data-lb-header-drawer aria-label="Menu">…mobile nav…</div>
</header>