Askr documentation
UI & Components

Navbar and Navigation Menu

Navbar and Navigation Menu: anatomy, keyboard behavior, state, and theming in Askr.

Example

import { NavigationMenu, NavigationMenuItem, NavigationMenuLink, NavigationMenuList } from '@askrjs/themes/components';

<NavigationMenu aria-label="Primary navigation">
  <NavigationMenuList>
    <NavigationMenuItem><NavigationMenuLink href={projectsRoute}>Projects</NavigationMenuLink></NavigationMenuItem>
    <NavigationMenuItem><NavigationMenuLink href={reportsRoute}>Reports</NavigationMenuLink></NavigationMenuItem>
  </NavigationMenuList>
</NavigationMenu>

Purpose

Despite the page title, the only export at @askrjs/themes/navigation-menu is NavigationMenu and its parts — there's no separate Navbar primitive on this subpath. Use these pieces to build a horizontal menu bar with flyout content: a top-level list of items, each optionally opening a panel of links. If you actually want the responsive app-header component (with a collapsing hamburger state), that's Navbar from the general themes component barrel, not this one.

Install and import

tsx import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuViewport, NavigationMenuIndicator, } from '@askrjs/themes/navigation-menu'; This subpath is an alias into the shared components barrel, so importing the same names from @askrjs/themes/components` works identically.

Live examples

The example composes NavigationMenu with a NavigationMenuList of NavigationMenuItem/NavigationMenuLink pairs, and passes route destinations rather than hand-written path strings. Note the aria-label on the menu itself: with more than one navigation landmark on a page, each needs its own name for that landmark list to be usable.

Anatomy

NavigationMenu renders a <nav>, NavigationMenuList an <ul>, and NavigationMenuItem an <li> — a plain nested list under the hood. NavigationMenuTrigger is a real <button type="button"> (with the nav-item class baked in), while NavigationMenuContent, NavigationMenuLink, NavigationMenuViewport, and NavigationMenuIndicator are generic slotted elements (a div, an anchor, a div, and a div respectively) that only exist to give you stable markup and data-slot hooks for the flyout panel, its links, the clipping viewport, and an active-tab indicator strip.

State model

None of these components track open/closed state, active item, or hover intent — there's no internal store or context here at all. You decide which trigger's content is visible, typically with a bit of local state or by composing NavigationMenuTrigger/NavigationMenuContent with the interaction primitives from @askrjs/ui. The themed Navbar gives you two different worked examples of that pattern, for two different jobs: its mobile hamburger collapse is built on Collapsible/CollapsibleTrigger/CollapsibleContent, while a per-item flyout (NavDropdown) is built on Dropdown/DropdownTrigger/DropdownContent — pick whichever matches what you're building rather than assuming one mechanism covers both. Treat this package as markup, not behavior.

Keyboard and accessibility

Because NavigationMenu is a <nav> and NavigationMenuTrigger is a genuine <button>, you get landmark navigation and native Tab/Enter/Space activation for free. What you don't get automatically is aria-expanded, aria-controls, or Escape-to-close on the trigger/content pair, or arrow-key movement across items — those are your responsibility to wire up if you're building a real dropdown-style flyout rather than a flat link list.

Styling and tokens

Every part carries a data-slot attribute — navigation-menu, navigation-menu-list, navigation-menu-item, navigation-menu-trigger, navigation-menu-content, navigation-menu-link, navigation-menu-viewport, navigation-menu-indicator — and the trigger additionally gets the nav-item class. Style off those hooks instead of assuming a fixed DOM shape, since as/asChild let consumers swap the underlying element.

API

NavigationMenu, NavigationMenuList, and NavigationMenuItem accept the shared CatalogComponentProps shape, including as to render a different tag. NavigationMenuTrigger is built on the same internal button-part helper as the rest of the catalog's button-flavored components, and that helper accepts asChild but silently discards as — passing it has no effect, it always renders a real <button> unless you use asChild. asChild itself hands rendering off to a Slot (useful for wrapping your router's Link) on every part, plus class, children, ref, and arbitrary passthrough attributes are supported throughout. NavigationMenuTrigger also accepts a type override, though it defaults to "button" so it never accidentally submits a form.

Edge cases

Multi-level or mega-menu content is entirely on you to lay out inside NavigationMenuContent — there's no built-in positioning or collision handling in NavigationMenuViewport, it's just a slotted div. Since there's no shipped open state, SSR is trivially safe (everything renders closed/static by default), but that also means client hydration has to attach whatever interaction logic you add. If you need real click-outside dismissal or focus trapping for a flyout, reach for @askrjs/ui's dropdown primitives rather than assuming this package provides it.

See Sidebar and Menubar for the other two navigation-chrome building blocks, Application Chrome for how these fit into a full page shell, and Breadcrumb and Pagination for the smaller wayfinding controls that often sit alongside a nav bar.