Menubar
An application menu bar.
Example
Installation
pnpm dlx shadcn@latest add superzero11/uiset/menubarIt lands in components/ui/menubar.tsx, with UISet’s root (uiset). Wrap your app in <UISet> once, and every component takes its skin.
What UISet adds
The component is UI SDK’s own, with its parts, props and names, so everything below works as UI SDK documents it. UISet adds the look and checks it:
- Styles in five skins, each in light and dark, from CSS variables you can override.
- Text at 4.5:1 contrast and control edges and focus rings at 3:1, in every skin and theme, checked with axe in tests.
- Haptic cues on phones, and sound cues when you set
soundon<UISet>. - Examples that name every control, so agents can find it (see Agents below).
The API reference and accessibility notes below are UI SDK’s, from its docs for @ui-sdk/react (Apache-2.0). Menubar on ui-sdk.dev has more usage examples. Your coding agent can read the same page at node_modules/@ui-sdk/react/docs/components/menubar.md.
API Reference
Menubar
orientation?(MenubarOrientation): Which arrow keys move between menus: left and right, or up and down. A vertical menubar opens its menus to the side.loop?(boolean): Whether arrow keys wrap from the last menu to the first, between triggers and between open menus.disabled?(boolean): Stops every menu from opening. Triggers stay focusable witharia-disabled, so the menubar keeps its tab stop and stays discoverable.modal?("pointer" | boolean): Themodalsetting of every menu in the menubar that doesn't set its own."pointer": an outside press only closes the open menu.true: also locks scroll, traps focus and makes the rest of the page inert, except the menubar.false: outside presses reach what they hit.controller?({ focus: () => void; isOpen: () => boolean; close: (options?: ChangeOptions<MenuOpenChangeReason>) => boolean; }): Opens, closes and focuses the menubar from anywhere. Create one withcreateMenubarController().debug?(boolean): Logs every state change with its reason to the console, in development builds.
Menus, items and their icons take the props on the Menu page, like MenuItemIcon.
State
orientation(MenubarOrientation): Which arrow keys move between menus, from theorientationoption. A vertical menubar opens its menus to the side.disabled(boolean): Whether the whole menubar is disabled.open(boolean): Whether one of its menus is open.modal(boolean | "pointer"): The modal setting its menus inherit.
Reasons
A menu that another menu in the menubar takes over closes with submenu-switch, one opened by pointing at its trigger opens with trigger-hover, and one opened with an arrow key opens with trigger-press. See Menu for every reason.
Controller
focus()(() => void): Moves focus to the trigger that holds the menubar's tab stop: the one used last, or the first.isOpen()(() => boolean): Whether one of its menus is open.close(options?)((options?: ChangeOptions<MenuOpenChangeReason> | undefined) => boolean): Closes the open menu, if any, and its submenus. Returns false when the close was canceled or it was already closed.
Accessibility
The menubar follows the WAI-ARIA menubar pattern.
Keyboard
Tab first lands on the first trigger, then remembers the last one used. While a menu is open, moving the pointer onto another enabled trigger opens its menu, unless the pointer is only passing over it on the way into the open menu. Pressing that trigger keeps it open. Moving to a disabled trigger closes the current menu without opening another.
| Key | On a Trigger | In an Open Menu |
|---|---|---|
| Left / Right | Previous or next trigger. | Previous or next menu, or enter/leave a submenu. |
| Down | Open at the first item. | Next item. |
| Up | Open at the last item. | Previous item. |
| Enter / Space | Open at the first item. | Choose the focused item. |
| Home / End | First or last trigger. | First or last item. |
| A letter | Find a trigger by name. | Find an item by name. |
| Escape | — | Close and return to the trigger. |
| Tab / Shift+Tab | Leave the bar. | Close and leave the bar. |
These directions describe a horizontal bar in left-to-right text. Right-to-left text reverses the horizontal keys and submenu side; vertical bars use Up and Down between triggers.
Tested Behavior
Every line below is an automated test.
- The menubar is named, and its triggers are menu items that open menus.
- The menubar is one tab stop, on its first trigger.
- Left and Right move between triggers, and Home goes to the first.
- Tab comes back to the trigger used last.
- ArrowDown opens a menu and focuses its first item.
- Enter opens a menu and focuses its first item.
- Space opens a menu and focuses its first item.
- Escape closes the menu and focuses its trigger.
- Tab in an open menu closes it and moves past the menubar.
- A press opens a menu, and a press on its trigger closes it.
- While a menu is open, pointing at another trigger opens its menu instead.
- Crossing another trigger on the way to the open menu keeps the menu.
- Resting on another trigger on the way to the open menu switches to it.
- While a menu is open, pointing at a disabled trigger closes it and opens nothing.
- After pointing at a disabled trigger, pointing at another trigger opens its menu.
- Pointing at a trigger while every menu is closed opens nothing.
- A press outside closes the open menu.
- Choosing an item runs it, closes the menu and focuses its trigger.
- Checkbox and radio items work in a menubar's menus.
- A disabled item in a menubar's menu is focusable and does nothing.
- With loop off, arrows stop at the ends.
Vertical
- Up and Down move between triggers, and Right opens a menu to the side.
Runs in React, Vue and web components.
Agents
UI SDK’s agent bridge finds this component by its accessible name: give it a visible label or an aria-label, and an agent can read its state and call its actions without screenshots. Building with AI agents shows how to connect one.
This is agent.snapshot() for the example above, read live from this page. Change something in the example, then read it again: the values follow.
[
{
"ref": "c8",
"component": "menu",
"actions": [
"setOpen",
"press"
],
"label": "File",
"open": false
},
{
"ref": "c9",
"component": "menu",
"actions": [
"setOpen",
"press"
],
"label": "View",
"open": false
},
{
"ref": "c10",
"component": "menubar",
"actions": [
"close"
],
"label": "Editor menu"
}
]