Menubar
A persistent application-command surface with real composite keyboard behavior and coordinated menus.
Choose a command bar
Use Menubar for persistent application commands such as Record and View in an editor. Use a navigation bar/rail for destinations and a Toolbar for direct frequent actions. A menubar role alone does not create composite keyboard behavior.
This wrapper now uses the installed Menubar primitive and attached Menu roots/triggers. Arrow movement, menu coordination and disabled behavior come from that installed implementation; ordinary independent buttons are not a complete menubar.
Anatomy and specifications
| Part | Actual contract |
|---|---|
| Menubar | Named div with installed menubar semantics, token surface/border/radius, 4px gap/padding; actual orientation also exposed through aria-orientation/data-orientation |
| Menu roots/triggers | Attached child menus; default triggers become command-bar menu items, min 44px height with visible focus outline |
| Popup commands | Named MenuPopup and real handlers/checkbox settings; same geometry as Menu |
| Vertical / narrow layout | Vertical flex column; horizontal items can wrap. Wrapping preserves logical composite order rather than a spatial grid-navigation claim. |
| Direction | DirectionProvider plus DOM dir govern installed navigation and reading context |
Working coordinated command bar
Local copies: 0.
Local command-bar notes are visible.
No local menubar command run. This bar operates this local example, not the site navigation.
'use client';
import { useState } from 'react';
import { Button, DirectionProvider, Menubar, Menu, MenuCheckboxItem, MenuCheckboxItemIndicator, MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuTrigger } from '@mobtranslate/ui';
export function MenubarCommandsExample() {
const [notes, setNotes] = useState(true);
const [count, setCount] = useState(0);
const [vertical, setVertical] = useState(false);
const [rtl, setRtl] = useState(false);
const [disabled, setDisabled] = useState(false);
const [result, setResult] = useState('No local menubar command run.');
const direction = rtl ? 'rtl' : 'ltr';
return <div data-menubar-lab className="space-y-4">
<div className="flex flex-wrap gap-3"><Button type="button" variant="outline" onClick={() => setVertical(current => !current)}>{vertical ? 'Use horizontal command bar' : 'Use vertical command bar'}</Button><Button type="button" variant="outline" onClick={() => setRtl(current => !current)}>{rtl ? 'Use LTR command context' : 'Use RTL command context'}</Button><Button type="button" variant="outline" onClick={() => setDisabled(current => !current)}>{disabled ? 'Enable command bar' : 'Disable command bar'}</Button></div>
<DirectionProvider direction={direction}><div dir={direction}>
<Menubar aria-label="Local example commands" orientation={vertical ? 'vertical' : 'horizontal'} disabled={disabled} className={state => state.orientation === 'vertical' ? 'local-menubar-vertical' : undefined}>
<Menu><MenuTrigger>Record</MenuTrigger><MenuPortal><MenuPositioner sideOffset={8}><MenuPopup aria-label="Local Record commands"><MenuItem onClick={() => { setCount(current => current+1); setResult('Added one local copy.'); }}>Add local copy</MenuItem><MenuItem disabled>Remote save (unavailable)</MenuItem></MenuPopup></MenuPositioner></MenuPortal></Menu>
<Menu><MenuTrigger>View</MenuTrigger><MenuPortal><MenuPositioner sideOffset={8}><MenuPopup aria-label="Local View commands"><MenuCheckboxItem checked={notes} onCheckedChange={setNotes}><MenuCheckboxItemIndicator keepMounted aria-hidden="true">✓</MenuCheckboxItemIndicator>Show bar notes</MenuCheckboxItem></MenuPopup></MenuPositioner></MenuPortal></Menu>
<Menu disabled><MenuTrigger>Unavailable</MenuTrigger></Menu>
</Menubar>
</div></DirectionProvider>
<p data-menubar-count>Local copies: {count}.</p>{notes && <p data-menubar-notes>Local command-bar notes are visible.</p>}
<Button type="button" variant="outline" onClick={() => { setCount(0); setNotes(true); setResult('Restored local command-bar state.'); }}>Restore command-bar state</Button>
<p role="status">{result} This bar operates this local example, not the site navigation.</p>
</div>;
}
API reference
| Prop | Installed contract |
|---|---|
| orientation | horizontal default; vertical alternative changes composite navigation and actual layout. |
| loopFocus | true default; controls wrapping at composite ends. |
| disabled | false default; disables attached top-level triggers. If availability changes while a popup is already open, close that menu or guard its commands explicitly. |
| modal | true default for open submenu interaction boundary; caller may choose false and verify outside behavior. |
| aria-label / aria-labelledby | Required meaningful bar name; label identifies the command scope, not the site name alone. |
| className / style / render / refs | Installed part contracts; styled wrappers retain string-or-state class callbacks. Default item/submenu refs target their actual elements. Custom renders must preserve events, roles, names and refs. |
Keyboard, state and local effects
- Tab enters/leaves the composite rather than visiting each command as an independent tab stop. Arrow navigation follows actual orientation and direction; opening a child menu uses installed menu behavior.
- The lab adds an actual local copy and toggles rendered notes. It includes an unavailable menu and whole-bar disable, plus independent restoration.
- Opening another top-level command coordinates the existing menu rather than leaving unrelated popups stacked. Check focus after selection, Escape and sibling movement.
- Vertical and RTL settings need DOM geometry plus actual key checks. A recorded pnpm patch synchronizes the installed Menu root’s parent context while closed, so a live orientation change is available before the next arrow key; it does not reset menu children or setting values. Pointer hover alone does not prove keyboard coordination.
Failures and recovery
- The pinned dependency patch lives in patches/@base-ui-components__react@1.0.0-rc.0.patch and is applied by the root pnpm manifest/lockfile. It updates CJS and ESM MenuRoot parent-context synchronization. Re-run the first-key orientation tests before removing it during an upgrade.
- The previous role-only div lacked installed composite state/navigation. New MenubarProps follow the installed primitive; state callback classes and orientation are now supported. Review event handlers that assumed independent tab stops.
- Do not use application-command menu semantics for every site link. Keep destinations available through ordinary navigation and reserve commands for actions.
- Narrow wrapping is not two-dimensional keyboard navigation. Prefer fewer meaningful groups or a task-specific compact command menu when a full bar becomes unwieldy.
- An unavailable command bar should not remove the task’s recovery path. Local command completion is not a saved record or permission check.
Verification and release scope
- Verify the actual command effect, requested/retained state, recovery and focus after activation. A closed menu or click callback is not a save receipt.
- Check trigger naming/expanded state, item roles, group names, checked states, disabled enforcement, keyboard entry/exit, type navigation, submenu return and focus restoration. Inspect the exact installed behavior rather than assuming an upstream convention.
- Check both themes, readable text/focus cues, long labels, bounded scrolling, 320px reflow, reduced motion, forced colors, zoom and the actual touch device. Context long press is not proven by mouse tests.
- Use the checked example sources and the menu-family browser suite, then a named screen-reader/browser pair for spoken output. These local English-only examples prove no server write, permission, source authority or qualified review.