Accessible, unstyled DropdownMenu: a menu button (WAI-ARIA APG) that opens a positioned menu. Items, groups, radio/checkbox items, and submenus are the shared Menu parts re-exported under DropdownMenu names.
|
|
| Package |
@vizejs/ui/dropdown-menu |
| Maturity |
stable |
| Own the source |
vize lib pull dropdown-menu |
| Requires |
menu, collection, context, controllable-state, dismissable-layer, focus-scope, id, inert-outside, pointer-grace, portal, positioner, presence, scroll-lock, typeahead |
| Aliases |
menu button, actions menu, overflow menu, kebab menu |
| Covers |
WAI-ARIA menu button pattern, Radix Dropdown Menu, Reka UI Dropdown 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 { DropdownMenu, DropdownMenuRoot, DropdownMenuTrigger } from "@vizejs/ui/dropdown-menu";
Or copy the source into your project with vize lib pull dropdown-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
<!-- Account menu button with grouped actions, a checkbox item, and a destructive item. -->
<script setup lang="ts">
import { ref } from "vue";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuItemIndicator,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@vizejs/ui/dropdown-menu";
const open = ref(false);
const away = ref(false);
const lastAction = ref("none");
</script>
<template>
<div>
<DropdownMenu v-model:open="open">
<DropdownMenuTrigger>Account</DropdownMenuTrigger>
<DropdownMenuContent aria-label="Account">
<DropdownMenuGroup>
<DropdownMenuLabel>Signed in as [email protected]</DropdownMenuLabel>
<DropdownMenuItem @select="() => (lastAction = 'Profile')">Profile</DropdownMenuItem>
<DropdownMenuItem @select="() => (lastAction = 'Billing')">Billing</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem v-model="away">
<DropdownMenuItemIndicator>✓</DropdownMenuItemIndicator>
Appear away
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuItem @select="() => (lastAction = 'Sign out')">Sign out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<output>Last action: {{ lastAction }}; away: {{ away ? "yes" : "no" }}</output>
</div>
</template>
API
Source: src/families/menus/dropdown-menu/dropdown-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/dropdown-menu/dropdown-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/dropdown-menu/dropdown-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. |
openOn |
"click" | "pointerdown" |
"pointerdown" |
Primary mouse button event that toggles the menu. pointerdown opens on press (native menu feel, press-drag-release selection); click waits for release. |
Events
| Event |
Payload |
Description |
pointerdown |
[nativeEvent: PointerEvent] |
Fired before a primary-button pointer-down toggles the menu; preventable. |
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. |
Behavior
@vizejs/ui/dropdown-menu is the WAI-ARIA APG menu button built on the shared menu core
(see ../menu/menu.behavior.md). dropdown-menu-root.vue publishes data-menu-kind="dropdown-menu";
dropdown-menu-trigger.vue adds press-to-open pointer semantics. Items, groups, submenus,
and content are the Menu SFCs re-exported under DropdownMenu* names. Every row is proven by
the named test in dropdown-menu.test.ts or dropdown-menu-ssr.test.ts.
| # |
State |
Input |
Outcome |
Proven by |
| D1 |
closed / open |
primary mouse pointer-down |
toggles; opening prevents trigger focus and focuses the content; the same press's click does not toggle again |
mouse pointer-down toggles the menu and focuses the content |
| D2 |
closed |
secondary button, ctrl-press, touch tap |
secondary and ctrl presses are ignored; touch taps toggle on click |
secondary buttons and ctrl-clicks do not toggle; touch taps toggle on click |
| D3 |
openOn="click" |
pointer-down then click |
only the click toggles |
openOn click waits for the click |
| D4 |
closed |
ArrowUp / keyboard or AT click (detail: 0) |
opens with the last or first item focused; Escape returns focus to the trigger |
keyboard and assistive-technology activation focus the first or last item |
| D5 |
closed |
prevented pointerdown / keydown emits |
state is unchanged |
preventable pointerdown and keydown emits keep the menu closed |
| D6 |
disabled trigger |
any activation |
native disabled; nothing opens |
disabled triggers ignore every activation |
| D7 |
open |
keyboard into a submenu and select |
shared menu parts: submenu keyboard entry, selection closes every level and returns focus to the trigger |
items and submenus are the shared menu parts |
| D8 |
SSR |
two requests + hydrate |
byte-identical markup with data-menu-kind="dropdown-menu" and warning-free hydration |
renders identical dropdown markup across SSR requests and hydrates cleanly |