Navbar
A responsive site header that collapses its links into a mobile panel below the md breakpoint.
Last updated
Uses JavaScript
Wires the mobile menu toggle: clicking it flips data-open on the
collapsible links panel and syncs aria-expanded.
Without JS: The toggle is hidden (`no-js:hidden`) and the links panel stays expanded (`no-js:flex`), so below the md breakpoint the nav is fully reachable without a working control to click. Desktop viewports (md and up) are unaffected: links show inline via CSS regardless of JS.
Show codeHide code
<Navbar>
<a slot="brand" href="/" class="text-lg font-semibold">Larkspur</a>
<NavLink href="/docs">Docs</NavLink>
<NavLink href="/pricing">Pricing</NavLink>
</Navbar>floating sets the bar on a rounded, raised pill:
Show codeHide code
<Navbar floating>…</Navbar>background sets the bar’s fill. page (the default) is the page colour, slightly
see-through over what scrolls under it. neutral-faded is a grey band. primary
is the brand colour: links, the menu button and the buttons in actions switch to
the on-brand colours on their own, and dropdown panels keep the page colours.
Show codeHide code
<Navbar background="primary">…</Navbar>For a dark bar on a light page, give it data-theme="dark".
Add
npx @ava-ui/apogee add navbar
No Apogee dependencies (compose the link slot with NavLink for
free active-state styling). Bring @lucide/astro for the mobile menu icon.
Ships one scoped JS island (progressive enhancement).
Usage
---
import Navbar from '@/components/apogee/Navbar/Navbar.astro'
import NavLink from '@/components/apogee/NavLink/NavLink.astro'
---
<Navbar>
<a slot="brand" href="/">Larkspur</a>
<NavLink href="/docs">Docs</NavLink>
<NavLink href="/pricing">Pricing</NavLink>
</Navbar>
Anatomy
brandslot — logo or wordmark, leftmost.- default slot — nav links. Inline row on desktop (md and up); a stacked panel below the header on mobile, hidden until the toggle opens it.
actionsslot — end-aligned buttons/controls. Rendered inline on desktop and again at the bottom of the mobile panel, so they stay reachable at every width. Don’t put elements with ids in it; they would be duplicated.toolsslot — icon buttons that stay in the bar at every width (a theme switch, a repository link), just before the menu toggle on phones. Rendered once, so ids are fine here.secondaryslot — a second row under the header, full width, for the active section’s tabs or pills. Scrolls sideways when it overflows.- mobile toggle — a hamburger button (
md:hidden), controls the links panel viaaria-controls/aria-expanded.
API
@composable — exposed to Content Editors in AVA. Any other attribute
passes through to the root <header> (...rest).
| Prop | Type | Default | Description |
|---|---|---|---|
floating |
boolean |
false |
The bar on a rounded, raised pill with space around it. |
background |
'page' | 'neutral-faded' | 'primary' |
'page' |
The bar’s fill. primary is the brand colour; everything in the bar takes the on-brand colours. |
centered |
boolean |
false |
Links in the middle, brand and actions at the edges. |
menuLabel |
string |
'Toggle menu' |
Name of the phone menu button. |
secondaryLabel |
string |
'Section' |
Name of the secondary nav row. |
class |
string |
— | Extra classes on the root <header>, merged last. |
States
- Collapsed (mobile, default) — the links panel is hidden below the md breakpoint until toggled open.
- Open (mobile) —
data-open="true"on the panel; toggle’saria-expanded="true". - Desktop (md and up) — links show inline always; the toggle and its panel logic don’t apply.
- No JS — see the JsNote above; the toggle is hidden and the links panel stays expanded.
Examples
With brand, links, and actions:
Show codeHide code
<Navbar>
<a slot="brand" href="/" class="text-lg font-semibold">Larkspur</a>
<NavLink href="/docs">Docs</NavLink>
<NavLink href="/pricing">Pricing</NavLink>
<NavLink href="/blog">Blog</NavLink>
<Button slot="actions" variant="ghost" color="neutral" href="/login">Log in</Button>
<Button slot="actions" href="/signup">Sign up</Button>
</Navbar>Icon buttons in tools stay in the bar on phones, next to the menu toggle:
Show codeHide code
<Navbar>
<a slot="brand" href="/">Larkspur</a>
<NavLink href="/docs">Docs</NavLink>
<NavLink href="/pricing">Pricing</NavLink>
<Button slot="actions" href="/signup">Sign up</Button>
<Tooltip slot="tools" text="Dark theme" placement="bottom">
<Button variant="ghost" color="neutral" iconOnly aria-label="Dark theme"><Moon /></Button>
</Tooltip>
</Navbar>A second row for the active section, and a centred layout:
Show codeHide code
<Navbar>
<a slot="brand" href="/">Larkspur</a>
<NavLink href="/docs" match="prefix">Docs</NavLink>
<NavLink href="/pricing">Pricing</NavLink>
<Tabs slot="secondary" variant="underline" tabs={[{ label: 'Overview' }, { label: 'Members' }, { label: 'Billing' }]} />
</Navbar>
<Navbar centered>…</Navbar>Accessibility
| Key | Action |
|---|---|
Tab |
Moves through the links, actions and menu toggle in DOM order. |
Enter Space |
On the toggle, open / close the mobile menu. |
Enter |
On a link, follows it. |
<header>/<nav>landmarks wrap the content.- The toggle is a real
<button>witharia-controlspointing at the panel’s id andaria-expandedsynced by JS;aria-label="Toggle menu"since it’s icon-only. - No JS — the toggle carries
no-js:hiddenand the links panel carriesno-js:flex, so on mobile the toggle disappears and the links render expanded — never a dead control, and the nav stays fully reachable. Desktop is unaffected — links and actions render inline via CSS regardless of JS.
Tokens
| Token | Used for |
|---|---|
--border-neutral |
Header bottom border / mobile panel border |
--background-page |
Header and mobile panel background |
--foreground-neutral |
Toggle icon colour |
--background-neutral-faded-highlighted |
Toggle hover background |
--outline-color-focus |
Toggle focus ring |
Do / Don’t
- Do put the primary call-to-action in
actions, not buried in the link list. - Do pair the default slot with
NavLinkso the current page gets its active state for free. - Don’t put elements with
ids in theactionsslot — it renders twice (inline on desktop, inside the mobile panel), so ids would collide. - Don’t assume the toggle is visible without JS — it’s
no-js:hiddenby design; the links panel takes over as the fallback instead.
Content
- Keep top-level link labels to one or two words; put anything longer in a dropdown or menu component instead.
- Give the
brandslot real link text, or anaria-label/altif it’s a logo image only.