Header

The full-width bar at the top of every (site) page. Contains the logo/name lockup (Avatar + title), desktop nav links, a Contact CTA, and a mobile menu trigger that opens MobileNav. It owns the shared state for both navs.

Live Preview

The real Header rendered inline. In production it sits at the top of the page and scrolls away with it.

Composition

Position

Absolute, not fixed — it belongs to the top of the page rather than the viewport, so it scrolls away like any other content. Out of flow, though: every page's first section already carries top padding sized to clear it, and putting it back in flow would double that up and open a band of flat background above the home hero's gradient.

Two navs, one owner

Header owns pathname and the MobileNav open state, and renders three siblings: this bar, the FloatingNav pill, and MobileNav. Both navs share their markup through NavCluster, which takes a variant of bar or pill. MobileNav must stay a sibling of both — as a fixed-position element, it would break if either became its containing block.

Work mega menu

Above lg, the Work item is a split control: the link still navigates to /work, and an adjacent chevron button opens a panel of up to six case studies with a “See all projects” footer. Hover opens it after a short intent delay (gated on a fine pointer), click and Enter toggle it, Escape closes and returns focus to the chevron. It follows the ARIA disclosure pattern, not a menubar — these are links, and menu semantics would have screen readers announce them as menu items.

The panel is opaque rather than the usual .popover-surface glass. The floating pill sets a backdrop-filter, which makes it a backdrop root — a translucent panel nested inside would sample the pill's own fill instead of the page, and render see-through with no blur. It would look correct in the bar and wrong in the pill, in one variant only.

Fewer than four case studies and the whole thing degrades to a plain link — a two-column grid holding two items is worse than no menu. A failed CMS fetch lands there too. The preview below can't show the panel: it is clipped by this box, and the header renders here without case-study data.

Composition

Avatar (photo variant, rotating gradient border, 32×32) + name lockup at body-sm / body-xs; nav links driven by a shared list, active item matched on pathname; a primary “Contact now” button that replaces the Contact link on desktop. Below lg the links hide and a hamburger opens MobileNav.

Every link shares one hover treatment: the label lifts to --foreground and a rounded-full fill in --foreground-overlay appears behind it. Work carries that fill on its wrapper rather than its anchor, so the label and the chevron read as one control and the two overlays can't stack into a heavier shade. The current page is deliberately left out — a fill means “you are pointing at this”, the accent dot means “you are here”.

Styling tokens

border-border-subtle + bg-[color-mix(in_srgb,var(--background)_60%,transparent)]px-4 sm:px-6 lg:px-8 gutter, h-8 row, max-w-[1360px] inner width

Import

tsx
import { Header } from '@/components/layout/Header'