Askr
UI & Components

Menubar

Use menubar with the current published Askr packages.

  • @askrjs/ui/menubar0.0.13
  • @askrjs/themes/menubar0.0.13

How to use menubar

Menubar should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep menubar labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Dropdown, DropdownContent, DropdownItem, DropdownTrigger } from '@askrjs/themes/components';

<Dropdown>
  <DropdownTrigger aria-label="Project actions">Actions</DropdownTrigger>
  <DropdownContent>
    <DropdownItem onSelect={archive}>Archive</DropdownItem>
    <DropdownItem onSelect={duplicate}>Duplicate</DropdownItem>
  </DropdownContent>
</Dropdown>

Purpose

Menubar is a horizontal bar of top-level menu triggers, each opening a dropdown of items — the pattern you'd recognize from desktop app menu bars (File, Edit, View), adapted for the web. Use it when you have several related command groups that need to stay visible and accessible without eating vertical space, as opposed to a single Dropdown for one-off actions. The behavior and ARIA wiring live in @askrjs/ui's Menubar; @askrjs/themes re-exports the same component unchanged and layers on CSS.

Install and import

Install both @askrjs/ui and @askrjs/themes, then import from @askrjs/ui/menubar for the unstyled primitive or @askrjs/themes/menubar if you want the default look applied automatically. Note that every @askrjs/themes subpath, including ./menubar, actually resolves to the same components.js barrel — the subpath is there for import-path clarity, not a smaller bundle. Bring in @askrjs/themes/default CSS once at the app root so the menubar tokens and data-slot styles are active.

Live examples

A typical menubar nests Menubar around several MenubarMenu blocks, each pairing a MenubarTrigger with a MenubarContent that holds MenubarItem, MenubarSeparator, and MenubarLabel. For nested submenus, wrap a MenubarSub around a MenubarSubTrigger and MenubarSubContent inside a menu's content. Because MenubarContent supports side, align, and sideOffset, you can flip a menu below or beside its trigger depending on where it sits in the layout.

Anatomy

The tree is Menubar > MenubarMenu > (MenubarTrigger, MenubarPortal > MenubarContent > (MenubarItem | MenubarGroup | MenubarLabel | MenubarSeparator | MenubarSub)). MenubarGroup and MenubarLabel exist purely for grouping and labeling related items inside a menu's content, similar to how you'd organize a native menu with section headers. MenubarPortal renders content outside the normal DOM flow so overlays aren't clipped by ancestor overflow or z-index stacking.

State model

Which menu is open is tracked internally by the Menubar root — you don't pass a controlled value or onChange for the open menu. The one prop you do control is `loop` on Menubar, which decides whether arrow-key navigation wraps from the last trigger back to the first. Individual triggers and items accept the usual disabled-style props exposed through ButtonLikeProps, letting you disable a menu or item without touching open-state logic.

Keyboard and accessibility

MENUBAR_A11Y_CONTRACT documents the exact contract: the root gets role="menubar", each MenubarContent gets role="menu", and each MenubarItem gets role="menuitem", with aria-expanded and aria-haspopup applied to triggers automatically. Arrow keys move focus across top-level triggers and down into open menus, matching the native menu bar convention users already expect. Data attributes like data-state, data-disabled, data-side, and data-align are set for you and are the correct hooks for styling — you shouldn't need to track any of that yourself.

Styling and tokens

@askrjs/themes styles Menubar entirely through attribute selectors like [data-slot="menubar"] and [data-slot="menubar-trigger"], reading color, radius, and spacing from the shared --ak-* custom properties (--ak-color-border, --ak-color-surface, --ak-radius-lg, and so on). Open/close transitions use the ak-menubar-in and ak-menubar-out keyframe animations defined alongside the component's CSS. Because the component ships with no inline styling of its own, overriding a token or writing a competing selector against the same data-slot attributes is enough to reskin it without forking markup.

API

Menubar takes MenubarOwnProps (id, loop) plus standard div props. MenubarTrigger and MenubarItem both accept ButtonLikeProps and support an asChild pattern for rendering your own element in the trigger's place. MenubarContent and MenubarSubContent share MenubarContentOwnProps — forceMount, side, align, sideOffset — so submenus position themselves the same way top-level menus do.

Edge cases

forceMount on MenubarContent keeps a menu's content mounted even when closed, which matters if you're animating exit transitions yourself or need the DOM node present for measurement before it opens. Submenus (MenubarSub) nest arbitrarily, but keep in mind each additional level adds another hop of arrow-key navigation for the user, so two levels deep is a reasonable practical ceiling. Because open state lives inside the component, you can't currently drive a menu open programmatically from outside it — plan your UI around user-triggered opens rather than external triggers.

If you only need a single dropdown of actions rather than a persistent bar of them, look at Dropdown Menu instead — it shares the same menu/item vocabulary without the top-level bar. Navbar and Navigation Menu covers site-level navigation, which is a different pattern from an application command bar even though both sit at the top of a page. For grouping unrelated overlay behavior like tooltips on menu items, see the Popover and Tooltip primitives in Foundations.