Accessible, unstyled Menu compound primitive (WAI-ARIA APG menu and menu button). Every menu surface — DropdownMenu, ContextMenu, and Menubar — shares this core: roving focus, typeahead, submenus with pointer grace, dismissal, focus return, positioning, portals, and presence.
|
|
| Package |
@vizejs/ui/menu |
| Maturity |
stable |
| Own the source |
vize lib pull menu |
| Requires |
collection, context, controllable-state, dismissable-layer, focus-scope, id, inert-outside, pointer-grace, portal, positioner, presence, scroll-lock, typeahead |
| Aliases |
menu, action menu, submenu, menuitemcheckbox, menuitemradio |
| Covers |
WAI-ARIA menu pattern, Radix Menu, Reka UI Menu, React Aria Menu |
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 { Menu, MenuRoot, MenuArrow, MenuCheckboxItem, MenuContent, MenuGroup, MenuItem, MenuItemIndicator, MenuLabel, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSub, MenuSubContent, MenuSubTrigger, MenuTrigger } from "@vizejs/ui/menu";
Or copy the source into your project with vize lib pull menu (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
<!-- Menu button with actions, a disabled item, and a checkbox item, opened via v-model:open. -->
<script setup lang="ts">
import { ref } from "vue";
import {
MenuCheckboxItem,
MenuContent,
MenuItem,
MenuItemIndicator,
MenuRoot,
MenuSeparator,
MenuTrigger,
} from "@vizejs/ui/menu";
const open = ref(false);
const pinned = ref(false);
const lastAction = ref("none");
</script>
<template>
<div>
<MenuRoot v-model:open="open">
<MenuTrigger>Document actions</MenuTrigger>
<MenuContent aria-label="Document actions">
<MenuItem @select="() => (lastAction = 'Rename')">Rename</MenuItem>
<MenuItem @select="() => (lastAction = 'Duplicate')">Duplicate</MenuItem>
<MenuItem disabled>Move to shared drive</MenuItem>
<MenuSeparator />
<MenuCheckboxItem v-model="pinned">
<MenuItemIndicator>✓</MenuItemIndicator>
Pin to sidebar
</MenuCheckboxItem>
</MenuContent>
</MenuRoot>
<output>Last action: {{ lastAction }}; pinned: {{ pinned ? "yes" : "no" }}</output>
</div>
</template>
API
Source: src/families/menus/menu/menu-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
true |
Make outside content inert and lock document scroll while open. |
dir |
MenuDirection |
undefined |
Reading direction: flips submenu arrow keys and submenu placement. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
false |
Wrap arrow-key navigation from the last item to the first and back. |
disabled |
boolean |
false |
Prevent opening and close the menu when it becomes disabled. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired with the requested open value (supports v-model:open). |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request with the previous value and native event. |
Slots
| Slot |
Slot props |
Description |
default |
MenuSlotState |
Trigger and content. Receives the menu open state. |
Source: src/families/menus/menu/menu-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
true |
Make outside content inert and lock document scroll while open. |
dir |
MenuDirection |
undefined |
Reading direction: flips submenu arrow keys and submenu placement. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
false |
Wrap arrow-key navigation from the last item to the first and back. |
disabled |
boolean |
false |
Prevent opening and close the menu when it becomes disabled. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired with the requested open value (supports v-model:open). |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request with the previous value and native event. |
Slots
| Slot |
Slot props |
Description |
default |
MenuSlotState |
Trigger and content. Receives the menu open state. |
Source: src/families/menus/menu/menu-arrow.vue
Slots
| Slot |
Slot props |
Description |
default |
MenuArrowSlotState |
Decorative arrow contents. Receives coordinates for optional custom drawing. |
Exposed
| Member |
Type |
Description |
x |
number | null |
Current arrow x coordinate when measured. |
y |
number | null |
Current arrow y coordinate when measured. |
element |
HTMLDivElement | null |
Rendered arrow element. |
Source: src/families/menus/menu/menu-checkbox-item.vue
Props
| Prop |
Type |
Default |
Description |
modelValue |
MenuCheckedState |
undefined |
Controlled checked value (v-model). undefined selects uncontrolled behavior. |
defaultValue |
MenuCheckedState |
false |
Initial checked value for uncontrolled use. |
disabled |
boolean |
false |
Block activation while keeping the item focusable. |
textValue |
string |
undefined |
Typeahead text when the rendered text is not representative. |
closeOnSelect |
boolean |
true |
Close the whole menu tree after an unprevented selection. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: boolean] |
Fired with the toggled checked value (supports v-model). |
select |
[event: MenuSelectEvent] |
Fired when the item is activated, before toggling; call preventDefault() to keep the menu open. |
Slots
| Slot |
Slot props |
Description |
default |
MenuCheckboxItemSlotState |
Item contents. Receives checked, highlight, and disabled state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered role="menuitem" element. |
id |
string |
Stable item id, also used as its collection key. |
highlighted |
boolean |
Whether the item owns the highlight. |
disabled |
boolean |
Whether activation is disabled. |
focus |
() => void |
Move focus (and the highlight) to this item. |
select |
(event?: Event | null) => boolean |
Activate the item as if the user selected it. |
checked |
MenuCheckedState |
Current checked value. |
Source: src/families/menus/menu/menu-content.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep the content mounted while the menu is closed. |
to |
string | HTMLElement |
"body" |
CSS selector or element the menu layer is moved into. |
portalDisabled |
boolean |
false |
Render in place instead of teleporting. |
defer |
boolean |
true |
Keep content in place until the target exists, avoiding SSR mismatch. |
placement |
Placement |
"bottom-start" |
Preferred placement before collision handling. |
strategy |
PositionerStrategy |
"fixed" |
CSS positioning mode published on the floating host. |
offset |
number |
4 |
Gap on the main axis between the reference and content. |
collisionPadding |
number |
4 |
Viewport padding the content should not cross. |
arrowPadding |
number |
0 |
Inset kept between the arrow and content edges. |
flip |
boolean |
true |
Flip to the opposite side when the preferred side overflows more. |
shift |
boolean |
true |
Shift the content back into the viewport after flip. |
size |
boolean |
false |
Constrain the host and publish positioner available-size CSS variables. |
safeArea |
boolean |
false |
Keep content clear of safe-area insets. |
hide |
boolean |
true |
Hide when the reference no longer intersects the viewport. |
updateOnScroll |
boolean |
true |
Recalculate while ancestors scroll. |
updateOnResize |
boolean |
true |
Recalculate when the viewport resizes. |
viewport |
Rect |
undefined |
Viewport used for flip, shift, and hide. |
closeOnEscape |
boolean |
true |
Let Escape close the menu. |
closeOnPointerDownOutside |
boolean |
true |
Let outside pointer-down close the menu. |
closeOnFocusOutside |
boolean |
true |
Let outside focus movement close the menu. |
ariaLabel |
string |
undefined |
Accessible name when the trigger does not label the menu. |
ariaLabelledby |
string |
undefined |
Ids that label the menu; defaults to the trigger id when a trigger exists. |
Events
| Event |
Payload |
Description |
open-auto-focus |
[event: MenuAutoFocusEvent] |
Fired before automatic entry focus; preventable. |
close-auto-focus |
[event: MenuAutoFocusEvent] |
Fired before focus returns to the trigger; preventable. |
escape-key-down |
[event: MenuEscapeKeyDownEvent] |
Fired before Escape closes the menu; preventable. |
pointer-down-outside |
[event: MenuPointerDownOutsideEvent] |
Fired before an outside pointer-down closes the menu; preventable. |
focus-outside |
[event: MenuFocusOutsideEvent] |
Fired before outside focus closes the menu; preventable. |
interact-outside |
[event: MenuInteractOutsideEvent] |
Fired before any outside interaction closes the menu; preventable. |
dismiss |
[event: MenuDismissEvent] |
Fired after an unprevented dismissal request. |
Slots
| Slot |
Slot props |
Description |
default |
MenuContentSlotState |
Menu items. Receives open state and the resolved placement. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered role="menu" element. |
open |
boolean |
Whether the content is open. |
state |
MenuState |
Stable state token. |
focusFirst |
() => HTMLElement | null |
Focus the first navigable item. |
focusLast |
() => HTMLElement | null |
Focus the last navigable item. |
focusContent |
() => void |
Focus the menu container and clear the highlight. |
Source: src/families/menus/menu/menu-group.vue
Slots
| Slot |
Slot props |
Description |
default |
— |
Grouped items, usually led by a MenuLabel that names the group. |
Source: src/families/menus/menu/menu-item.vue
Props
| Prop |
Type |
Default |
Description |
disabled |
boolean |
false |
Block activation. The item stays focusable and announced as disabled (WAI-ARIA APG). |
textValue |
string |
undefined |
Typeahead text when the rendered text is not representative. |
closeOnSelect |
boolean |
true |
Close the whole menu tree after an unprevented selection. |
Events
| Event |
Payload |
Description |
select |
[event: MenuSelectEvent] |
Fired when the item is activated; call preventDefault() to keep the menu open. |
Slots
| Slot |
Slot props |
Description |
default |
MenuItemSlotState |
Item contents. Receives highlight and disabled state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered role="menuitem" element. |
id |
string |
Stable item id, also used as its collection key. |
highlighted |
boolean |
Whether the item owns the highlight. |
disabled |
boolean |
Whether activation is disabled. |
focus |
() => void |
Move focus (and the highlight) to this item. |
select |
(event?: Event | null) => boolean |
Activate the item as if the user selected it. |
Source: src/families/menus/menu/menu-item-indicator.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep the indicator visible and its slot rendered while unchecked, for exit animations or glyphs that style the unchecked state themselves. |
Slots
| Slot |
Slot props |
Description |
default |
MenuItemIndicatorSlotState |
Indicator glyph. Receives the owning item's checked-state token. |
Source: src/families/menus/menu/menu-label.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned label id. null and undefined select a deterministic fallback. |
Slots
| Slot |
Slot props |
Description |
default |
— |
Visible label text. Inside a MenuGroup it names that group. |
Source: src/families/menus/menu/menu-radio-group.vue
Props
| Prop |
Type |
Default |
Description |
modelValue |
Value | null |
undefined |
Controlled value (v-model). undefined selects uncontrolled behavior; null clears. |
defaultValue |
Value | null |
null |
Initial value for uncontrolled use. |
disabled |
boolean |
false |
Block activation of every radio item in the group. |
equals |
(left: Value, right: Value) => boolean |
Object.is |
Equality used to decide which item is checked (for example, compare object ids). |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: Value | null] |
Fired with the newly selected value (supports v-model). |
value-change |
[value: Value | null, previous: Value | null, nativeEvent: Event | null] |
Fired after a distinct value is selected, with the previous value and native event. |
Slots
| Slot |
Slot props |
Description |
default |
MenuRadioGroupSlotState<Value> |
Radio items. Receives the current value. |
Exposed
| Member |
Type |
Description |
value |
Value | null |
Current group value. |
setValue |
(value: Value | null) => boolean |
Request a group value; returns whether it changed. |
Source: src/families/menus/menu/menu-radio-item.vue
Props
| Prop |
Type |
Default |
Description |
value (required) |
Value |
undefined |
Value committed to the enclosing MenuRadioGroup when this item is selected. |
disabled |
boolean |
false |
Block activation while keeping the item focusable. |
textValue |
string |
undefined |
Typeahead text when the rendered text is not representative. |
closeOnSelect |
boolean |
true |
Close the whole menu tree after an unprevented selection. |
Events
| Event |
Payload |
Description |
select |
[event: MenuSelectEvent] |
Fired when the item is activated, before the group value changes; preventable. |
Slots
| Slot |
Slot props |
Description |
default |
MenuRadioItemSlotState |
Item contents. Receives checked, highlight, and disabled state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered role="menuitem" element. |
id |
string |
Stable item id, also used as its collection key. |
highlighted |
boolean |
Whether the item owns the highlight. |
disabled |
boolean |
Whether activation is disabled. |
focus |
() => void |
Move focus (and the highlight) to this item. |
select |
(event?: Event | null) => boolean |
Activate the item as if the user selected it. |
checked |
boolean |
Whether this item holds the group value. |
Source: src/families/menus/menu/menu-separator.vue
Source: src/families/menus/menu/menu-sub.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned submenu base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use (only honored while the parent is open). |
disabled |
boolean |
false |
Prevent the submenu from opening; its trigger stays focusable but inert. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired with the requested submenu open value (supports v-model:open). |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request with the previous value and native event. |
Slots
| Slot |
Slot props |
Description |
default |
MenuSubSlotState |
MenuSubTrigger and MenuSubContent. Receives the submenu open state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the submenu is open. |
state |
MenuState |
Stable state token. |
triggerId |
string |
Id wired to the submenu trigger. |
contentId |
string |
Id wired to the submenu content. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request the submenu open value. |
Source: src/families/menus/menu/menu-sub-content.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep the submenu mounted while closed. |
to |
string | HTMLElement |
"body" |
CSS selector or element the submenu layer is moved into. |
portalDisabled |
boolean |
false |
Render in place instead of teleporting. |
defer |
boolean |
true |
Keep content in place until the target exists, avoiding SSR mismatch. |
strategy |
PositionerStrategy |
"fixed" |
CSS positioning mode published on the floating host. |
offset |
number |
0 |
Gap between the submenu trigger and the submenu. |
collisionPadding |
number |
4 |
Viewport padding the submenu should not cross. |
arrowPadding |
number |
0 |
Inset kept between the arrow and content edges. |
flip |
boolean |
true |
Flip to the opposite side when the reading-direction side overflows more. |
shift |
boolean |
true |
Shift the submenu back into the viewport after flip. |
size |
boolean |
false |
Constrain the host and publish positioner available-size CSS variables. |
safeArea |
boolean |
false |
Keep content clear of safe-area insets. |
hide |
boolean |
true |
Hide when the trigger no longer intersects the viewport. |
updateOnScroll |
boolean |
true |
Recalculate while ancestors scroll. |
updateOnResize |
boolean |
true |
Recalculate when the viewport resizes. |
viewport |
Rect |
undefined |
Viewport used for flip, shift, and hide. |
closeOnEscape |
boolean |
true |
Let Escape close this submenu and return focus to its trigger. |
closeOnPointerDownOutside |
boolean |
true |
Let outside pointer-down close the submenu (and the tree when outside every menu). |
closeOnFocusOutside |
boolean |
true |
Let outside focus movement close the submenu. |
ariaLabel |
string |
undefined |
Accessible name overriding the submenu trigger label. |
Events
| Event |
Payload |
Description |
open-auto-focus |
[event: MenuAutoFocusEvent] |
Fired before automatic entry focus; preventable. |
close-auto-focus |
[event: MenuAutoFocusEvent] |
Fired before automatic focus restoration; preventable. |
escape-key-down |
[event: MenuEscapeKeyDownEvent] |
Fired before Escape closes the submenu; preventable. |
pointer-down-outside |
[event: MenuPointerDownOutsideEvent] |
Fired before an outside pointer-down closes the submenu; preventable. |
focus-outside |
[event: MenuFocusOutsideEvent] |
Fired before outside focus closes the submenu; preventable. |
interact-outside |
[event: MenuInteractOutsideEvent] |
Fired before any outside interaction closes the submenu; preventable. |
dismiss |
[event: MenuDismissEvent] |
Fired after an unprevented dismissal request. |
Slots
| Slot |
Slot props |
Description |
default |
MenuContentSlotState |
Submenu items. Receives open state and the resolved placement. |
Source: src/families/menus/menu/menu-sub-trigger.vue
Props
| Prop |
Type |
Default |
Description |
disabled |
boolean |
false |
Block opening the submenu while keeping the trigger focusable. |
textValue |
string |
undefined |
Typeahead text when the rendered text is not representative. |
Slots
| Slot |
Slot props |
Description |
default |
MenuSubTriggerSlotState |
Trigger contents. Receives submenu open state plus highlight and disabled state. |
Source: src/families/menus/menu/menu-trigger.vue
Props
| Prop |
Type |
Default |
Description |
disabled |
boolean |
false |
Remove the trigger from activation and sequential keyboard focus. |
ariaLabel |
string |
undefined |
Accessible name when no visible label supplies one. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before a click toggles the menu. Call preventDefault() to keep state unchanged. |
keydown |
[nativeEvent: KeyboardEvent] |
Fired before keyboard handling. Call preventDefault() to skip the built-in keys. |
Slots
| Slot |
Slot props |
Description |
default |
MenuSlotState |
Trigger contents. Receives the menu open state. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the trigger. |
Behavior
Normative state x input -> outcome table for the shared menu core
(@vizejs/ui/menu), which also powers dropdown-menu, context-menu, and
menubar. It follows the WAI-ARIA APG menu button
and menu patterns. Every row is proven
by the named test in menu.test.ts, menu-keyboard.test.ts, menu-pointer.test.ts, or
menu-ssr.test.ts; compile-only guarantees live in menu.types.test-d.ts.
Components: menu-root.vue, menu-trigger.vue, menu-content.vue, menu-item.vue,
menu-checkbox-item.vue, menu-radio-group.vue, menu-radio-item.vue,
menu-item-indicator.vue, menu-group.vue, menu-label.vue, menu-separator.vue,
menu-sub.vue, menu-sub-trigger.vue, menu-sub-content.vue, menu-arrow.vue.
| # |
State |
Input |
Outcome |
Proven by |
| M1 |
closed |
render / pointer click on trigger |
native button with aria-haspopup="menu", aria-expanded, aria-controls while open; content is role="menu" labelled by the trigger and focused itself |
renders menu-button ARIA, deterministic ids, and pointer-opened content |
| M2 |
closed, trigger focus |
Enter / Space / ArrowDown / ArrowUp |
opens and focuses (and highlights) the first item, or the last item for ArrowUp |
trigger ${key} opens the menu and focuses ${expected} (four cases) |
| M3 |
open |
ArrowDown/Up, Home/End, PageDown/PageUp |
roving focus moves one item or to either end; no wrap unless loop |
arrow, Home/End, and Page keys move focus without wrapping by default |
| M4 |
open, loop |
ArrowUp on first / ArrowDown on last |
focus wraps |
loop wraps arrow navigation at both ends |
| M5 |
open |
printable characters |
locale-aware typeahead: prefix match, repeated letters cycle, textValue overrides rendered text |
typeahead matches prefixes, cycles repeated letters, and honors textValue |
| M6 |
open, pending query |
Space |
extends the query instead of selecting; Space selects once the query expires |
Space extends a pending typeahead query instead of selecting |
| M7 |
open (any depth) |
Tab |
the whole tree closes and focus returns to the trigger |
Tab closes the whole menu tree and returns focus to the trigger |
| M8 |
item highlighted |
Enter / click |
select fires with a preventable MenuSelectEvent; unprevented selection closes the tree and returns focus; preventDefault() keeps it open |
selecting an item emits a preventable select, closes the tree, and returns focus |
| M9 |
disabled item |
arrows / Enter / click |
stays focusable (APG), exposes aria-disabled and data-disabled, never selects |
disabled items stay focusable, announce aria-disabled, and never select |
| M10 |
checkbox item |
activation |
toggles aria-checked (mixed for indeterminate), data-state, indicator visibility, v-model; closeOnSelect=false keeps the menu open |
checkbox items toggle aria-checked, indicators, and honor closeOnSelect |
| M11 |
radio group <Value> |
activation |
commits the item's typed value, compares with equals, emits update:modelValue and value-change |
radio groups select generic values with custom equality |
| M12 |
controlled open |
trigger activation |
emits update:open and open-change; rendered state follows the prop only |
controlled open emits requests without opening until the parent accepts |
| M13 |
open |
outside pointer-down / Escape |
outside pointer closes without stealing focus; Escape closes and returns focus; closeOn* props disable either path |
outside pointer-down closes without stealing focus; Escape closes and returns focus |
| M14 |
open, modal |
render |
outside content is inert and scroll-locked (trigger stays live); non-modal menus leave the page interactive |
modal menus inert outside content; non-modal menus leave the page interactive |
| M15 |
disabled root |
activation / becoming disabled |
refuses to open and closes an open menu |
disabled roots refuse to open and close when disabled while open |
| M16 |
closed, forceMount |
render |
content stays mounted with hidden and data-state="closed"; no document controllers |
force-mounted closed content stays hidden and inactive |
| M17 |
any |
expose |
root openMenu/close/toggle/setOpen and ids; content focusFirst/focusLast/focusContent |
exposes root and content imperative APIs |
| M18 |
submenu trigger |
ArrowRight / Enter / Space |
opens the submenu and focuses its first item; ArrowLeft or Escape closes only the submenu and refocuses its trigger |
submenu keys: ArrowRight/Enter open, ArrowLeft/Escape close to the sub trigger |
| M19 |
dir="rtl" |
ArrowLeft / ArrowRight |
open/close keys swap and the submenu is placed on the left |
rtl flips submenu arrow keys and placement |
| M20 |
disabled MenuSub |
activation |
sub trigger stays focusable with aria-disabled and never opens |
disabled submenu triggers stay focusable but do not open |
| M21 |
open, mouse |
pointer move / leave on items |
hover highlights (focuses) items; leaving clears the highlight back to the menu |
hovering items moves the highlight; leaving an item clears it |
| M22 |
open |
hover disabled item / touch move |
disabled items never take the highlight; touch pointers never highlight |
hovering a disabled item clears the highlight; touch pointers never highlight |
| M23 |
open, mouse |
hover submenu trigger |
opens the submenu without moving focus into it; hovering submenu items highlights them |
hovering a submenu trigger opens it without moving focus into it |
| M24 |
submenu open |
pointer travels toward the submenu |
the pointer-grace safe triangle keeps the submenu open and suppresses sibling highlight; leaving it highlights the sibling and closes the submenu |
pointer grace keeps the submenu open along the safe triangle and closes it outside |
| M25 |
grace armed |
300 ms elapse |
the triangle expires even if the pointer rests inside it |
pointer grace expires after its delay |
| M26 |
submenu open |
enter submenu / outside pointer-down |
entering clears the grace; pointer-down outside every level closes the tree |
entering the submenu clears the grace; clicking outside every menu closes the tree |
| M27 |
submenu open |
pointer-down in the parent menu |
closes only the submenu |
pointer-down inside the parent menu closes only the submenu |
| M28 |
submenu open |
select submenu item |
selection closes every level |
clicking a submenu item selects it and closes every level |
| M29 |
submenu closed |
pointer click on submenu trigger |
opens the submenu |
clicking a submenu trigger with a pointer opens it |
| M30 |
SSR, open |
two isolated requests |
byte-identical markup, content rendered in place, no inert/scroll-lock/highlight side effects |
renders deterministic open menu markup on the server |
| M31 |
SSR markup |
hydrate |
no mismatch diagnostics, root node reused, then portal and focus activate |
hydrates without mismatches and then activates focus |
Extension hooks
| Hook |
Values |
| parts |
trigger, content-host, content, sub-content-host, sub-content, item, sub-trigger, item-indicator, group, radio-group, label, separator, arrow |
| data |
data-vize-ui, data-state (open/closed, or checked/unchecked/indeterminate), data-highlighted, data-disabled, data-menu-kind, data-modal, data-side, data-align, data-placement, data-top-layer |
| CSS vars |
Positioner variables such as --vize-ui-positioner-available-width when size is enabled |
| composable |
useMenuRoot() builds custom root surfaces on the same tree/level contract |