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.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
MenubarContentOwnProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
MenubarContentProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
MenubarGroupAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
MenubarGroupProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
MenubarItemAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
textValue | textValue?: string | undefined; | — | Text used for typeahead when rendered children are not plain text. |
type | type?: undefined; | — | — |
MenubarItemProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
textValue | textValue?: string | undefined; | — | Text used for typeahead when rendered children are not plain text. |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
MenubarLabelAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
MenubarLabelProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
MenubarMenuProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
value | value?: string | undefined; | — | Stable and unique among sibling menus when menus are rendered dynamically. |
MenubarOwnProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
id | id?: string | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
MenubarPortalProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
MenubarProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
id | id?: string | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
MenubarSeparatorAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
MenubarSeparatorProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
MenubarSubContentAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
MenubarSubContentProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
MenubarSubProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
value | value?: string | undefined; | — | — |
MenubarSubTriggerAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
textValue | textValue?: string | undefined; | — | Text used for typeahead when rendered children are not plain text. |
type | type?: undefined; | — | — |
MenubarSubTriggerProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
textValue | textValue?: string | undefined; | — | Text used for typeahead when rendered children are not plain text. |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
MenubarTriggerAsChildProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
textValue | textValue?: string | undefined; | — | Text used for typeahead when rendered children are not plain text. |
type | type?: undefined; | — | — |
MenubarTriggerProps
Import from @askrjs/ui/menubar.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
textValue | textValue?: string | undefined; | — | Text used for typeahead when rendered children are not plain text. |
type | type?: "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.
Related pages
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.