Accessible, unstyled ContextMenu: opens a menu at the pointer on contextmenu, at the focused element on Shift+F10 or the ContextMenu key, and at the touch point after a long press. Items and submenus are the shared Menu parts re-exported under ContextMenu names.
|
|
| Package |
@vizejs/ui/context-menu |
| Maturity |
stable |
| Own the source |
vize lib pull context-menu |
| Requires |
long-press, menu, press, collection, context, controllable-state, dismissable-layer, focus-scope, id, inert-outside, pointer-grace, portal, positioner, presence, scroll-lock, typeahead |
| Aliases |
right-click menu, contextual menu, long-press menu, shortcut menu |
| Covers |
WAI-ARIA menu pattern, Radix Context Menu, Reka UI Context 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 { ContextMenu, ContextMenuRoot, ContextMenuTrigger } from "@vizejs/ui/context-menu";
Or copy the source into your project with vize lib pull context-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
<!-- Right-click (or Shift+F10) menu for a file row with actions and a sort radio group. -->
<script setup lang="ts">
import { ref } from "vue";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuItemIndicator,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@vizejs/ui/context-menu";
const lastAction = ref("none");
const sortBy = ref<string | null>("name");
</script>
<template>
<div>
<ContextMenu>
<ContextMenuTrigger>report-2026.pdf</ContextMenuTrigger>
<ContextMenuContent aria-label="File actions">
<ContextMenuItem @select="() => (lastAction = 'Open')">Open</ContextMenuItem>
<ContextMenuItem @select="() => (lastAction = 'Rename')">Rename</ContextMenuItem>
<ContextMenuItem disabled>Share</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuLabel>Sort by</ContextMenuLabel>
<ContextMenuRadioGroup v-model="sortBy">
<ContextMenuRadioItem value="name">
<ContextMenuItemIndicator>•</ContextMenuItemIndicator>
Name
</ContextMenuRadioItem>
<ContextMenuRadioItem value="modified">
<ContextMenuItemIndicator>•</ContextMenuItemIndicator>
Date modified
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
<output>Last action: {{ lastAction }}; sorted by {{ sortBy }}</output>
</div>
</template>
API
Source: src/families/menus/context-menu/context-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; the menu anchors at the viewport origin until a context-menu request supplies a point. |
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 |
Ignore context-menu requests so the native browser menu shows instead. |
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 |
ContextMenuTrigger and menu content. Receives the menu open state. |
Source: src/families/menus/context-menu/context-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; the menu anchors at the viewport origin until a context-menu request supplies a point. |
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 |
Ignore context-menu requests so the native browser menu shows instead. |
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 |
ContextMenuTrigger and menu content. Receives the menu open state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the menu content is visible and interactive. |
state |
MenuState |
Stable state token for styling and tests. |
modal |
boolean |
Whether outside content is inert and scroll-locked while open. |
dir |
MenuDirection |
Resolved reading direction. |
id |
string |
Root-owned base id. |
triggerId |
string |
Id wired to the trigger. |
contentId |
string |
Id wired to the content. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request an open value; returns whether it changed. |
openMenu |
(entry?: MenuEntryFocus, event?: Event | null) => boolean |
Open the menu and move focus to entry once content mounts. |
close |
(event?: Event | null) => boolean |
Close the whole menu tree. |
toggle |
(event?: Event | null) => boolean |
Toggle the menu. |
openAt |
(point: ContextMenuPoint, event?: Event | null) => boolean |
Open the menu anchored at a viewport point, as a context-menu request would. |
Source: src/families/menus/context-menu/context-menu-trigger.vue
Props
| Prop |
Type |
Default |
Description |
disabled |
boolean |
false |
Let the native browser context menu show for this region. |
longPressDelay |
number |
700 |
Milliseconds a touch must stay down before the menu opens at the touch point. |
Events
| Event |
Payload |
Description |
contextmenu |
[nativeEvent: MouseEvent] |
Fired before a contextmenu request opens the menu; call preventDefault() to skip it. |
Slots
| Slot |
Slot props |
Description |
default |
ContextMenuTriggerSlotState |
Region contents. Receives the menu open state and trigger availability. |
Exposed
| Member |
Type |
Description |
element |
HTMLSpanElement | null |
Rendered trigger region. |
Behavior
@vizejs/ui/context-menu opens the shared menu core (see ../menu/menu.behavior.md) anchored
at a viewport point. context-menu-root.vue owns a virtual anchor and restores focus to
whatever held it before the request; context-menu-trigger.vue is a non-interactive region that
translates contextmenu, Shift+F10, the ContextMenu key, and touch long-press into requests.
Every row is proven by the named test in context-menu.test.ts or context-menu-ssr.test.ts.
| # |
State |
Input |
Outcome |
Proven by |
| C1 |
closed |
contextmenu (right click) |
prevents the native menu, anchors at the pointer, opens data-menu-kind="context-menu" content, focuses the menu |
contextmenu opens at the pointer, prevents the native menu, and focuses the menu |
| C2 |
open |
new request elsewhere |
the outside press closes, the request reopens the same content at the new anchor |
a new request while open re-anchors the same open menu |
| C3 |
closed, focus |
Shift+F10 / ContextMenu key |
anchors at the focused element, focuses the first item; Escape returns focus to the requesting element |
Shift+F10 and the ContextMenu key open at the focused element with the first item focused |
| C4 |
closed |
plain F10 / other keys |
nothing opens |
plain F10 and other keys do not open the menu |
| C5 |
closed, touch |
press held for longPressDelay |
opens at the touch point; shorter taps do nothing |
a touch long press opens at the touch point; short taps do not |
| C6 |
closed, mouse |
press held |
mouse presses never use the long-press path |
mouse presses never trigger the long-press path |
| C7 |
disabled |
contextmenu |
trigger or root disabled lets the native menu show and publishes data-disabled |
disabled triggers and roots let the native menu show |
| C8 |
enabled |
prevented contextmenu emit |
the native menu is kept and nothing opens |
a prevented contextmenu emit keeps the native menu |
| C9 |
open |
select / outside pointer-down |
selection closes; outside pointer-down closes |
selecting closes; outside pointer-down closes without restoring focus |
| C10 |
any |
openAt(point) expose |
opens programmatically with the first item focused |
exposes openAt for programmatic requests |
| C11 |
SSR |
two requests + hydrate |
byte-identical markup, no long-press description leakage, warning-free hydration |
renders identical context-menu markup across SSR requests and hydrates cleanly |