SiteHeader

The sticky top bar, with a burger menu below the md breakpoint.

The header stays pinned to the top, with a backdrop blur.
<SiteHeader.Root>
	<SiteHeader.Bar>
		<SiteHeader.Brand href="/"><BrandLogo /></SiteHeader.Brand>
		<SiteHeader.Nav>
			<SiteHeader.NavList>
				<SiteHeader.NavItem href="/#pricing">Pricing</SiteHeader.NavItem>
			</SiteHeader.NavList>
			<SiteHeader.Actions>
				<Button size="sm">I'm hiring</Button>
			</SiteHeader.Actions>
		</SiteHeader.Nav>
		<SiteHeader.MobileToggle aria-label="Open or close the menu" />
	</SiteHeader.Bar>
	<SiteHeader.MobileMenu>
		<SiteHeader.MobileNavItem href="/#pricing">Pricing</SiteHeader.MobileNavItem>
	</SiteHeader.MobileMenu>
</SiteHeader.Root>

MobileToggle and MobileMenu share their open state through the root, so the two lists are declared separately but only one is ever visible.

The mobile menu

Below the breakpoint the menu is a full-screen overlay under the bar: its list scrolls on its own, MobileActions pins its buttons to the bottom (clear of the home indicator), and following a MobileNavItem closes it. While it is open the page behind is frozen through useScrollLock — import @voila.dev/ui/styles/landing.css once, which turns the flag into overflow: hidden and releases it past the breakpoint on its own.

collapseBelow="xl" keeps the burger until xl, for a header with more links and buttons than a tablet can hold:

<SiteHeader.Root collapseBelow="xl">…</SiteHeader.Root>

open/onOpenChange control the menu, so it can be mutually exclusive with another full-screen surface such as a cart drawer — which can lock the page with the same hook:

const [cartOpen, setCartOpen] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
useScrollLock(cartOpen);
 
<SiteHeader.Root
	open={menuOpen}
	onOpenChange={(next) => {
		setMenuOpen(next);
		if (next) setCartOpen(false);
	}}
>

API

SiteHeader.Root

PropTypeDefault
collapseBelowSiteHeaderCollapse"md"The width under which the burger menu replaces the desktop nav. A header with more links and buttons than md can hold collapses until xl instead of wrapping.
defaultOpenbooleanfalseWhether the mobile menu starts open. Leave it off: the menu server-renders closed and hydrates, so opening by default flashes on first paint.
onOpenChange((open: boolean) => void)—
openboolean—Controlled open state — pair with onOpenChange.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <header> prop. Source: landing/components/site-header/root.tsx.

SiteHeader.Actions

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <div> prop. Source: landing/components/site-header/actions.tsx.

PropTypeDefault
aria-labelstring—Defines a string value that labels the current element.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <nav> prop. Source: landing/components/site-header/bar.tsx.

SiteHeader.Brand

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <a> prop. Source: landing/components/site-header/brand.tsx.

SiteHeader.MobileActions

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <li> prop. Source: landing/components/site-header/mobile-actions.tsx.

SiteHeader.MobileMenu

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <div> prop. Source: landing/components/site-header/mobile-menu.tsx.

SiteHeader.MobileNavItem

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <a> prop. Source: landing/components/site-header/mobile-nav-item.tsx.

SiteHeader.MobileToggle

PropTypeDefault
aria-labelstringrequiredDefines a string value that labels the current element.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <button> prop. Source: landing/components/site-header/mobile-toggle.tsx.

SiteHeader.Nav

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <div> prop. Source: landing/components/site-header/nav.tsx.

SiteHeader.NavItem

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <a> prop. Source: landing/components/site-header/nav-item.tsx.

SiteHeader.NavList

PropTypeDefault
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <ul> prop. Source: landing/components/site-header/nav-list.tsx.