Askr documentation
UI & Components

Menubar

Menubar: anatomy, keyboard behavior, state, and theming in Askr.

Example

import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarTrigger } from '@askrjs/themes/components';

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>Project</MenubarTrigger>
    <MenubarContent>
      <MenubarItem onPress={rename}>Rename</MenubarItem>
      <MenubarItem onPress={archive}>Archive</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Published props

Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.

MenubarContentAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
asChildasChild: true;
childrenchildren: JSXElement;
forceMountforceMount?: boolean | undefined;
refref?: Ref<Element>;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

MenubarContentOwnProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
forceMountforceMount?: boolean | undefined;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

MenubarContentProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
asChildasChild?: false | undefined;
childrenchildren?: unknown;
forceMountforceMount?: boolean | undefined;
refref?: Ref<HTMLDivElement>;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

MenubarGroupAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

MenubarGroupProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

MenubarItemAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
textValuetextValue?: string | undefined;Text used for typeahead when rendered children are not plain text.
typetype?: undefined;

MenubarItemProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
textValuetextValue?: string | undefined;Text used for typeahead when rendered children are not plain text.
typetype?: "button" | "submit" | "reset" | undefined;

MenubarLabelAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

MenubarLabelProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

MenubarMenuProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
childrenchildren?: unknown;
valuevalue?: string | undefined;Stable and unique among sibling menus when menus are rendered dynamically.

MenubarOwnProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
idid?: string | undefined;
looploop?: boolean | undefined;

MenubarPortalProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
childrenchildren?: unknown;

MenubarProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
idid?: string | undefined;
looploop?: boolean | undefined;
refref?: Ref<HTMLDivElement>;

MenubarSeparatorAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

MenubarSeparatorProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

MenubarSubContentAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
asChildasChild: true;
childrenchildren: JSXElement;
forceMountforceMount?: boolean | undefined;
refref?: Ref<Element>;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

MenubarSubContentProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
asChildasChild?: false | undefined;
childrenchildren?: unknown;
forceMountforceMount?: boolean | undefined;
refref?: Ref<HTMLDivElement>;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

MenubarSubProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
childrenchildren?: unknown;
valuevalue?: string | undefined;

MenubarSubTriggerAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
textValuetextValue?: string | undefined;Text used for typeahead when rendered children are not plain text.
typetype?: undefined;

MenubarSubTriggerProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
textValuetextValue?: string | undefined;Text used for typeahead when rendered children are not plain text.
typetype?: "button" | "submit" | "reset" | undefined;

MenubarTriggerAsChildProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
textValuetextValue?: string | undefined;Text used for typeahead when rendered children are not plain text.
typetype?: undefined;

MenubarTriggerProps

Import from @askrjs/ui/menubar.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
textValuetextValue?: string | undefined;Text used for typeahead when rendered children are not plain text.
typetype?: "button" | "submit" | "reset" | undefined;

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.