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 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 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.

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

  • brand slot — 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.
  • actions slot — 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.
  • tools slot — 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.
  • secondary slot — 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 via aria-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’s aria-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 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 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 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> with aria-controls pointing at the panel’s id and aria-expanded synced by JS; aria-label="Toggle menu" since it’s icon-only.
  • No JS — the toggle carries no-js:hidden and the links panel carries no-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 NavLink so the current page gets its active state for free.
  • Don’t put elements with ids in the actions slot — 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:hidden by 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 brand slot real link text, or an aria-label / alt if it’s a logo image only.