Accessible, unstyled Menubar (WAI-ARIA APG menubar): a horizontal row of menu triggers with roving focus, typeahead, and Left/Right hand-off between open menus. Menu content, items, and submenus are the shared Menu parts re-exported under Menubar names.
|
|
| Package |
@vizejs/ui/menubar |
| Maturity |
stable |
| Own the source |
vize lib pull menubar |
| Requires |
menu, collection, context, controllable-state, dismissable-layer, focus-scope, id, inert-outside, pointer-grace, portal, positioner, presence, scroll-lock, typeahead |
| Aliases |
application menu bar, menu bar, top menu |
| Covers |
WAI-ARIA menubar pattern, Radix Menubar, Reka UI Menubar |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Menubar, MenubarRoot, MenubarMenu, MenubarTrigger } from "@vizejs/ui/menubar";
Or copy the source into your project with vize lib pull menubar (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Application menubar with File and Edit menus whose open menu is bound with v-model. -->
<script setup lang="ts">
import { ref } from "vue";
import {
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarRoot,
MenubarSeparator,
MenubarTrigger,
} from "@vizejs/ui/menubar";
const openMenu = ref<string | null>(null);
const lastCommand = ref("none");
</script>
<template>
<div>
<MenubarRoot v-model="openMenu" aria-label="Editor">
<MenubarMenu value="file">
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem @select="() => (lastCommand = 'New file')">New file</MenubarItem>
<MenubarItem @select="() => (lastCommand = 'Open')">Open…</MenubarItem>
<MenubarSeparator />
<MenubarItem disabled>Export as PDF</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu value="edit">
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem @select="() => (lastCommand = 'Undo')">Undo</MenubarItem>
<MenubarItem @select="() => (lastCommand = 'Redo')">Redo</MenubarItem>
</MenubarContent>
</MenubarMenu>
</MenubarRoot>
<output>Last command: {{ lastCommand }}</output>
</div>
</template>
API
Source: src/families/menus/menubar/menubar-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned menubar id. null and undefined select a deterministic fallback. |
modelValue |
string | null |
undefined |
Controlled value of the open menu (v-model); null closes every menu. undefined selects uncontrolled behavior. |
defaultValue |
string | null |
null |
Initially open menu for uncontrolled use. |
dir |
MenuDirection |
undefined |
Reading direction: flips Left/Right arrow meaning across triggers and menus. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
true |
Wrap Left/Right navigation from the last trigger to the first and back. |
ariaLabel |
string |
undefined |
Accessible name for the menubar. |
ariaLabelledby |
string |
undefined |
Ids that label the menubar. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: string | null] |
Fired with the requested open-menu value (supports v-model). |
value-change |
[value: string | null, previous: string | null, nativeEvent: Event | null] |
Fired after a distinct open-menu change with the previous value and native event. |
Slots
| Slot |
Slot props |
Description |
default |
MenubarSlotState |
MenubarMenu children. Receives the open-menu value. |
Source: src/families/menus/menubar/menubar-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned menubar id. null and undefined select a deterministic fallback. |
modelValue |
string | null |
undefined |
Controlled value of the open menu (v-model); null closes every menu. undefined selects uncontrolled behavior. |
defaultValue |
string | null |
null |
Initially open menu for uncontrolled use. |
dir |
MenuDirection |
undefined |
Reading direction: flips Left/Right arrow meaning across triggers and menus. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
true |
Wrap Left/Right navigation from the last trigger to the first and back. |
ariaLabel |
string |
undefined |
Accessible name for the menubar. |
ariaLabelledby |
string |
undefined |
Ids that label the menubar. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: string | null] |
Fired with the requested open-menu value (supports v-model). |
value-change |
[value: string | null, previous: string | null, nativeEvent: Event | null] |
Fired after a distinct open-menu change with the previous value and native event. |
Slots
| Slot |
Slot props |
Description |
default |
MenubarSlotState |
MenubarMenu children. Receives the open-menu value. |
Exposed
| Member |
Type |
Description |
value |
string | null |
Value of the open menu, or null when every menu is closed. |
dir |
MenuDirection |
Resolved reading direction. |
element |
HTMLDivElement | null |
Rendered role="menubar" element. |
setValue |
(value: string | null) => boolean |
Open the menu with value (or close every menu with null). |
focus |
() => void |
Move focus to the first enabled trigger. |
Source: src/families/menus/menubar/menubar-menu.vue
Props
| Prop |
Type |
Default |
Description |
value |
string |
undefined |
Stable value identifying this menu in the menubar v-model. undefined selects a deterministic fallback. |
modal |
boolean |
false |
Make outside content inert while this menu is open. Menubars default to non-modal so hovering sibling triggers can switch menus. |
loop |
boolean |
false |
Wrap Up/Down navigation inside this menu. |
disabled |
boolean |
false |
Prevent this menu from opening; its trigger stays focusable per WAI-ARIA APG. |
Slots
| Slot |
Slot props |
Description |
default |
MenubarMenuSlotState |
MenubarTrigger and menu content. Receives this menu's open state and value. |
Exposed
| Member |
Type |
Description |
value |
string |
Stable value identifying this menu. |
open |
boolean |
Whether this menu is open. |
setOpen |
(value: boolean) => boolean |
Open this menu (entry focus on the first item) or close it. |
Source: src/families/menus/menubar/menubar-trigger.vue
Props
| Prop |
Type |
Default |
Description |
disabled |
boolean |
false |
Keep the trigger focusable but prevent it from opening its menu. |
textValue |
string |
undefined |
Typeahead text when the rendered text is not representative. |
Slots
| Slot |
Slot props |
Description |
default |
MenubarTriggerSlotState |
Trigger contents. Receives menu state, value, and roving highlight. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered role="menuitem" trigger. |
focus |
() => void |
Move focus (and the roving tab stop) to this trigger. |
Behavior
@vizejs/ui/menubar implements the WAI-ARIA APG menubar pattern on the shared menu core
(see ../menu/menu.behavior.md). menubar-root.vue owns the open-menu value and a roving
tab stop across triggers; each menubar-menu.vue is a non-modal menu root; menubar-trigger.vue
is a role="menuitem" button. Every row is proven by the named test in menubar.test.ts or
menubar-ssr.test.ts.
| # |
State |
Input |
Outcome |
Proven by |
| B1 |
any |
render |
role="menubar", horizontal orientation, label, menuitem triggers with aria-haspopup; one tabindex="0" stop |
renders a labelled horizontal menubar with a single roving tab stop |
| B2 |
trigger focus |
ArrowLeft/Right, Home/End, characters |
roving focus moves (wrapping by default), jumps to ends, and typeahead matches trigger text |
Left/Right move the roving focus with wrapping, Home/End jump |
| B3 |
dir="rtl" |
ArrowLeft |
moves to the next trigger |
rtl flips Left/Right across triggers |
| B4 |
trigger focus |
ArrowDown/Enter/Space, ArrowUp, Escape |
opens with the first or last item focused and updates v-model; Escape closes back to the trigger |
ArrowDown/Enter/Space open with the first item; ArrowUp with the last |
| B5 |
menu open |
ArrowRight/Left on a plain item |
closes the menu and opens the adjacent one with its first item focused, wrapping |
Right/Left inside an open menu hand off to the adjacent menu |
| B6 |
submenu in a menu |
ArrowRight / ArrowRight in submenu |
a submenu trigger consumes ArrowRight; ArrowRight on a plain submenu item hands off to the next menu |
a submenu trigger consumes Right; Left closes the submenu before handing off |
| B7 |
menu open |
ArrowRight on a focused trigger |
the next menu opens |
Right on a trigger while a menu is open opens the next menu |
| B8 |
mouse |
pointer-down / hover siblings |
pointer-down toggles; while a menu is open, hovering another trigger switches the open menu |
pointer-down toggles a menu and hovering siblings switches the open menu |
| B9 |
menu open |
render / outside pointer-down |
menubar menus are non-modal; outside pointer-down closes |
menubar menus are non-modal and close on outside pointer-down |
| B10 |
disabled menu |
navigation / activation |
its trigger remains focusable with aria-disabled and never opens |
disabled menus keep a focusable trigger that never opens |
| B11 |
controlled value |
selection / props / expose |
the prop wins until accepted; setValue and focus drive the root |
controlled value and the exposed API drive the open menu |
| B12 |
SSR |
two requests + hydrate |
byte-identical markup with a single tab stop and warning-free hydration |
renders identical menubar markup across SSR requests and hydrates cleanly |