<script setup lang="ts">
import {
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuItemIndicator,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from 'akar';
import { ref } from 'vue';
import './styles.css';
const checkboxOne = ref(false);
const checkboxTwo = ref(false);
const person = ref('pedro');
function handleClick() {
// eslint-disable-next-line no-alert
alert('hello!');
}
</script>
<template>
<div class="akar-demo akar-demo-ContextMenu">
<ContextMenuRoot>
<ContextMenuTrigger
as-child
class="ContextMenuTrigger"
>
<span> Right click here. </span>
</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent
class="ContextMenuContent"
:side-offset="5"
>
<ContextMenuItem
value="New Tab"
class="ContextMenuItem"
@click="handleClick"
>
New Tab <div
class="RightSlot"
>
⌘+T
</div>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger
value="more toolsz"
class="ContextMenuSubTrigger"
>
More Tools <div
class="RightSlot"
>
<PIcon name="i-lucide-chevron-right" />
</div>
</ContextMenuSubTrigger>
<ContextMenuPortal>
<ContextMenuSubContent
class="ContextMenuSubContent"
:side-offset="2"
:align-offset="-5"
>
<ContextMenuItem
class="ContextMenuItem"
>
Save Page As… <div
class="RightSlot"
>
⌘+S
</div>
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Create Shortcut…
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Name Window…
</ContextMenuItem>
<ContextMenuSeparator class="ContextMenuSeparator" />
<ContextMenuItem
class="ContextMenuItem"
>
Developer Tools
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuPortal>
</ContextMenuSub>
<ContextMenuItem
value="New Window"
class="ContextMenuItem"
>
New Window <div
class="RightSlot"
>
⌘+N
</div>
</ContextMenuItem>
<ContextMenuItem
value="New Private Window"
class="ContextMenuItem"
disabled
>
New Private Window <div
class="RightSlot"
>
⇧+⌘+N
</div>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger
value="more tools"
class="ContextMenuSubTrigger"
>
More Tools <div
class="RightSlot"
>
<PIcon name="i-lucide-chevron-right" />
</div>
</ContextMenuSubTrigger>
<ContextMenuPortal>
<ContextMenuSubContent
class="ContextMenuSubContent"
:side-offset="2"
:align-offset="-5"
>
<ContextMenuItem
class="ContextMenuItem"
>
Save Page As… <div
class="RightSlot"
>
⌘+S
</div>
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Create Shortcut…
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Name Window…
</ContextMenuItem>
<ContextMenuSeparator class="ContextMenuSeparator" />
<ContextMenuItem
class="ContextMenuItem"
>
Developer Tools
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger
value="more toolsz"
class="ContextMenuSubTrigger"
>
More Tools <div
class="RightSlot"
>
<PIcon name="i-lucide-chevron-right" />
</div>
</ContextMenuSubTrigger>
<ContextMenuPortal>
<ContextMenuSubContent
class="ContextMenuSubContent"
:side-offset="2"
:align-offset="-5"
>
<ContextMenuItem
class="ContextMenuItem"
>
Save Page As… <div
class="RightSlot"
>
⌘+S
</div>
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Create Shortcut…
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Name Window…
</ContextMenuItem>
<ContextMenuSeparator class="ContextMenuSeparator" />
<ContextMenuItem
class="ContextMenuItem"
>
Developer Tools
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger
value="more toolsz"
class="ContextMenuSubTrigger"
>
More Tools <div
class="RightSlot"
>
<PIcon name="i-lucide-chevron-right" />
</div>
</ContextMenuSubTrigger>
<ContextMenuPortal>
<ContextMenuSubContent
class="ContextMenuSubContent"
:side-offset="2"
:align-offset="-5"
>
<ContextMenuItem
class="ContextMenuItem"
>
Save Page As… <div
class="RightSlot"
>
⌘+S
</div>
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Create Shortcut…
</ContextMenuItem>
<ContextMenuItem
class="ContextMenuItem"
>
Name Window…
</ContextMenuItem>
<ContextMenuSeparator class="ContextMenuSeparator" />
<ContextMenuItem
class="ContextMenuItem"
>
Developer Tools
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuPortal>
</ContextMenuSub>
</ContextMenuSubContent>
</ContextMenuPortal>
</ContextMenuSub>
<ContextMenuItem
class="ContextMenuItem"
>
Developer Tools
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuPortal>
</ContextMenuSub>
<ContextMenuSeparator class="ContextMenuSeparator" />
<ContextMenuCheckboxItem
v-model="checkboxOne"
class="ContextMenuItem"
>
<ContextMenuItemIndicator class="ContextMenuItemIndicator">
<PIcon name="i-lucide-check" />
</ContextMenuItemIndicator> Show Bookmarks <div
class="RightSlot"
>
⌘+B
</div>
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
v-model="checkboxTwo"
class="ContextMenuItem"
>
<ContextMenuItemIndicator class="ContextMenuItemIndicator">
<PIcon name="i-lucide-check" />
</ContextMenuItemIndicator> Show Full URLs
</ContextMenuCheckboxItem>
<ContextMenuSeparator class="ContextMenuSeparator" />
<ContextMenuLabel class="ContextMenuLabel">
People
</ContextMenuLabel>
<ContextMenuRadioGroup v-model="person">
<ContextMenuRadioItem
class="ContextMenuItem"
value="pedro"
>
<ContextMenuItemIndicator class="ContextMenuItemIndicator">
<PIcon name="i-lucide-circle" />
</ContextMenuItemIndicator> Pedro Duarte
</ContextMenuRadioItem>
<ContextMenuRadioItem
class="ContextMenuItem"
value="colm"
>
<ContextMenuItemIndicator class="ContextMenuItemIndicator">
<PIcon name="i-lucide-circle" />
</ContextMenuItemIndicator> Colm Tuite
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/mauve.css';
@import '../_colors/grass.css';
@scope (.akar-demo-ContextMenu) {
.ContextMenuTrigger {
display: block;
border: 2px white dashed;
color: white;
border-radius: 4px;
font-size: 15px;
user-select: none;
padding: 45px 0;
width: 300px;
text-align: center;
}
.ContextMenuContent,
.ContextMenuSubContent {
min-width: 220px;
background-color: white;
border-radius: 6px;
overflow: hidden;
padding: 5px;
box-shadow:
0px 10px 38px -10px rgba(22, 23, 24, 0.35),
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
}
.ContextMenuItem,
.ContextMenuCheckboxItem,
.ContextMenuRadioItem,
.ContextMenuSubTrigger {
font-size: 13px;
line-height: 1;
color: var(--grass-11);
border-radius: 3px;
display: flex;
align-items: center;
height: 25px;
padding: 0 5px;
position: relative;
padding-left: 25px;
user-select: none;
outline: none;
}
.ContextMenuSubTrigger[data-state='open'] {
background-color: var(--grass-4);
color: var(--grass-11);
}
.ContextMenuItem[data-disabled],
.ContextMenuCheckboxItem[data-disabled],
.ContextMenuRadioItem[data-disabled],
.ContextMenuSubTrigger[data-disabled] {
color: var(--mauve-8);
pointer-events: 'none';
}
.ContextMenuItem[data-highlighted],
.ContextMenuCheckboxItem[data-highlighted],
.ContextMenuRadioItem[data-highlighted],
.ContextMenuSubTrigger[data-highlighted] {
background-color: var(--grass-9);
color: var(--grass-1);
}
.ContextMenuLabel {
padding-left: 25px;
font-size: 12px;
line-height: 25px;
color: var(--mauve-11);
}
.ContextMenuSeparator {
height: 1px;
background-color: var(--grass-6);
margin: 5px;
}
.ContextMenuItemIndicator {
position: absolute;
left: 0;
width: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.RightSlot {
margin-left: auto;
padding-left: 20px;
color: var(--mauve-11);
}
[data-highlighted] > .RightSlot {
color: white;
}
[data-disabled] .RightSlot {
color: var(--mauve-8);
}
}Features
- Supports submenus with configurable reading direction.
- Supports items, labels, groups of items.
- Supports checkable items (single or multiple) with optional indeterminate state.
- Supports modal and non-modal modes.
- Customize side, alignment, offsets, collision handling.
- Focus is fully managed.
- Full keyboard navigation.
- Typeahead support.
- Dismissing and layering behavior is highly customizable.
- Triggers with a long-press on touch devices
Installation
Install the component from your command line.
pnpm add akar
yarn add akar
npm install akar
bun add akar
Anatomy
Import all parts and piece them together.
<script setup lang="ts">
import {
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuItemIndicator,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from 'akar';
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger />
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuLabel />
<ContextMenuItem />
<ContextMenuGroup>
<ContextMenuItem />
</ContextMenuGroup>
<ContextMenuCheckboxItem>
<ContextMenuItemIndicator />
</ContextMenuCheckboxItem>
<ContextMenuRadioGroup>
<ContextMenuRadioItem>
<ContextMenuItemIndicator />
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
<ContextMenuSub>
<ContextMenuSubTrigger />
<ContextMenuPortal>
<ContextMenuSubContent />
</ContextMenuPortal>
</ContextMenuSub>
<ContextMenuSeparator />
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
API Reference
Adheres to the Menu WAI-ARIA design pattern and uses roving tabindex to manage focus movement among menu items.
Root
Contains all the parts of a context menu.
| Prop | Default | Type |
|---|---|---|
dir | "ltr" | "rtl"The reading direction of the combobox when applicable. If omitted, inherits globally from | |
modal | true | booleanThe modality of the dropdown menu. When set to |
pressOpenDelay | 700 | numberThe duration from when the trigger is pressed until the menu opens. |
| Event | Payload |
|---|---|
update:open | payload: boolean |
Trigger
The area that opens the context menu. Wrap it around the target you want the context menu to open from when right-clicking (or using the relevant keyboard shortcuts).
| Prop | Default | Type |
|---|---|---|
as | "span" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | false | booleanWhen Note that this will also restore the native context menu. |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Portal
When used, portals the content part into the body.
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) | |
disabled | booleanDisable teleport and render the component inline | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop |
Content
The component that pops out in an open context menu.
| Prop | Default | Type |
|---|---|---|
alignFlip | true | booleanFlip alignment when colliding with boundary.
May only occur when |
alignOffset | 0 | numberAn offset in pixels from the |
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | true | booleanWhen |
collisionBoundary | [] | Element | (Element | null)[] | nullThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. |
collisionPadding | 0 | number | Partial<Record<"top" | "right" | "bottom" | "left", number>>The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: . |
disableUpdateOnLayoutShift | false | booleanWhether to disable the update position for the content when the layout shifted. |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideShiftedArrow | true | booleanWhen |
hideWhenDetached | false | booleanWhether to hide the content when the trigger becomes fully occluded. |
loop | false | booleanWhen |
memoDependencies | unknown[]Reactive dependencies that should invalidate the memoized content subtree. | |
positionStrategy | "fixed" | "absolute"The type of CSS position property to use. | |
prioritizePosition | false | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. |
reference | ReferenceElementThe custom element or virtual element that will be set as the reference to position the floating element. If provided, it will replace the default anchor element. | |
sideFlip | true | booleanFlip to the opposite side when colliding with boundary. |
sticky | "partial" | "partial" | "always"The sticky behavior on the align axis. |
| Event | Payload |
|---|---|
closeAutoFocus | event: EventEvent handler called when auto-focusing on close. Can be prevented. |
escapeKeyDown | event: KeyboardEventEvent handler called when the escape key is down. Can be prevented. |
focusOutside | event: FocusOutsideEventEvent handler called when the focus moves outside of the |
interactOutside | event: PointerDownOutsideEvent | FocusOutsideEventEvent handler called when an interaction happens outside the |
openAutoFocus | event: Event |
pointerDownOutside | event: PointerDownOutsideEventEvent handler called when a |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--akar-context-menu-content-transform-origin | The transform-origin computed from the content and arrow positions/offsets |
--akar-context-menu-content-available-width | The remaining width between the trigger and the boundary edge |
--akar-context-menu-content-available-height | The remaining height between the trigger and the boundary edge |
--akar-context-menu-trigger-width | The width of the trigger |
--akar-context-menu-trigger-height | The height of the trigger |
Arrow
An optional arrow element to render alongside a submenu. This can be used to help visually link the trigger item with the ContextMenu.Content. Must be rendered inside ContextMenu.Content.
| Prop | Default | Type |
|---|---|---|
as | "svg" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | 5 | numberThe height of the arrow in pixels. |
rounded | false | booleanWhen |
width | 10 | numberThe width of the arrow in pixels. |
Item
The component that contains the context menu items.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the Use this when the content is complex, or you have non-textual content inside. |
| Event | Payload |
|---|---|
select | event: EventEvent handler called when the user selects an item (via mouse or keyboard). Calling |
| Data attribute | Value |
|---|---|
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
Group
Used to group multiple ContextMenu.Items.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Label
Used to render a label. It won't be focusable using arrow keys.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
CheckboxItem
An item that can be controlled and rendered like a checkbox.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
modelValue | false | true | "indeterminate"The controlled checked state of the item. Can be used as | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the Use this when the content is complex, or you have non-textual content inside. |
| Event | Payload |
|---|---|
select | event: EventEvent handler called when the user selects an item (via mouse or keyboard). Calling |
update:modelValue | payload: booleanEvent handler called when the checked state changes. |
| Slot (default) | Payload |
|---|---|
checked | false | true | "indeterminate"Current checked state |
modelValue | CheckedStateCurrent modelValue state |
| Data attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
RadioGroup
Used to group multiple ContextMenu.RadioItems.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
modelValue | AcceptableValueThe value of the selected item in the group. |
| Event | Payload |
|---|---|
update:modelValue | payload: AcceptableValueEvent handler called when the value changes. |
RadioItem
An item that can be controlled and rendered like a radio.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the Use this when the content is complex, or you have non-textual content inside. | |
value* | AcceptableValueThe unique value of the item. |
| Event | Payload |
|---|---|
select | event: EventEvent handler called when the user selects an item (via mouse or keyboard). Calling |
| Slot (default) | Payload |
|---|---|
checked | booleanCurrent checked state |
| Data attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
ItemIndicator
Renders when the parent ContextMenu.CheckboxItem or ContextMenu.RadioItem is checked. You can style this element directly, or you can use it as a wrapper to put an icon into, or both.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Data attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
Separator
Used to visually separate items in the context menu.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Sub
Contains all the parts of a submenu.
| Prop | Default | Type |
|---|---|---|
defaultOpen | booleanThe open state of the submenu when it is initially rendered. Use when you do not need to control its open state. | |
open | booleanThe controlled open state of the menu. Can be used as |
| Event | Payload |
|---|---|
update:open | payload: booleanEvent handler called when the open state of the submenu changes. |
| Slot (default) | Payload |
|---|---|
open | booleanCurrent open state |
SubTrigger
An item that opens a submenu. Must be rendered inside ContextMenu.Sub.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
graceDuration | 300 | numberTime in milliseconds to keep a submenu open while the pointer moves from its trigger to the submenu. |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the Use this when the content is complex, or you have non-textual content inside. |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
SubContent
The component that pops out when a submenu is open. Must be rendered inside ContextMenu.Sub.
| Prop | Default | Type |
|---|---|---|
alignFlip | true | booleanFlip alignment when colliding with boundary.
May only occur when |
alignOffset | 0 | numberAn offset in pixels from the |
arrowPadding | 0 | numberThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. |
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | true | booleanWhen |
collisionBoundary | [] | Element | (Element | null)[] | nullThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. |
collisionPadding | 0 | number | Partial<Record<"top" | "right" | "bottom" | "left", number>>The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: . |
disableUpdateOnLayoutShift | false | booleanWhether to disable the update position for the content when the layout shifted. |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideShiftedArrow | true | booleanWhen |
hideWhenDetached | false | booleanWhether to hide the content when the trigger becomes fully occluded. |
loop | false | booleanWhen |
memoDependencies | unknown[]Reactive dependencies that should invalidate the memoized content subtree. | |
positionStrategy | "fixed" | "absolute"The type of CSS position property to use. | |
prioritizePosition | false | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. |
reference | ReferenceElementThe custom element or virtual element that will be set as the reference to position the floating element. If provided, it will replace the default anchor element. | |
sideFlip | true | booleanFlip to the opposite side when colliding with boundary. |
sideOffset | 0 | numberThe distance in pixels from the trigger. |
sticky | "partial" | "partial" | "always"The sticky behavior on the align axis. |
updatePositionStrategy | 'optimized' | "always" | "optimized"Strategy to update the position of the floating element on every animation frame. |
| Event | Payload |
|---|---|
closeAutoFocus | event: EventEvent handler called when auto-focusing on close. Can be prevented. |
entryFocus | event: Event |
escapeKeyDown | event: KeyboardEventEvent handler called when the escape key is down. Can be prevented. |
focusOutside | event: FocusOutsideEventEvent handler called when the focus moves outside of the |
interactOutside | event: PointerDownOutsideEvent | FocusOutsideEventEvent handler called when an interaction happens outside the |
openAutoFocus | event: Event |
pointerDownOutside | event: PointerDownOutsideEventEvent handler called when a |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--akar-context-menu-content-transform-origin | The transform-origin computed from the content and arrow positions/offsets |
--akar-context-menu-content-available-width | The remaining width between the trigger and the boundary edge |
--akar-context-menu-content-available-height | The remaining height between the trigger and the boundary edge |
--akar-context-menu-trigger-width | The width of the trigger |
--akar-context-menu-trigger-height | The height of the trigger |
Examples
With submenus
You can create submenus by using ContextMenuSub in combination with its parts.
<script setup lang="ts">
import {
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuSub>
<ContextMenuSubTrigger>Sub menu →</ContextMenuSubTrigger>
<ContextMenuPortal>
<ContextMenuSubContent>
<ContextMenuItem>Sub menu item</ContextMenuItem>
<ContextMenuItem>Sub menu item</ContextMenuItem>
<ContextMenuArrow />
</ContextMenuSubContent>
</ContextMenuPortal>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem>…</ContextMenuItem>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
With disabled items
You can add special styles to disabled items via the data-disabled attribute.
<script setup lang="ts">
import { ContextMenuContent, ContextMenuItem, ContextMenuPortal, ContextMenuRoot, ContextMenuTrigger } from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuItem
class="ContextMenuItem"
disabled
>
…
</ContextMenuItem>
<ContextMenuItem class="ContextMenuItem">
…
</ContextMenuItem>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
/* styles.css */
.ContextMenuItem[data-disabled] {
color: gainsboro;
}
With separators
Use the Separator part to add a separator between items.
<script setup lang="ts">
import {
ContextMenuContent,
ContextMenuItem,
ContextMenuPortal,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuTrigger,
} from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>…</ContextMenuItem>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
With labels
Use the Label part to help label a section.
<script setup lang="ts">
import {
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRoot,
ContextMenuTrigger,
} from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuLabel>Label</ContextMenuLabel>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuItem>…</ContextMenuItem>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
With checkbox items
Use the CheckboxItem part to add an item that can be checked.
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import {
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuItemIndicator,
ContextMenuPortal,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuTrigger,
} from 'akar'
const checked = ref(true)
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuItem>…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuCheckboxItem v-model="checked">
<ContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</ContextMenuItemIndicator>
Checkbox item
</ContextMenuCheckboxItem>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
With radio items
Use the RadioGroup and RadioItem parts to add an item that can be checked amongst others.
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import {
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuItemIndicator,
ContextMenuPortal,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuTrigger,
} from 'akar'
const color = ref('blue')
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuRadioGroup v-model="color">
<ContextMenuRadioItem value="red">
<ContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</ContextMenuItemIndicator>
Red
</ContextMenuRadioItem>
<ContextMenuRadioItem value="blue">
<ContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</ContextMenuItemIndicator>
Blue
</ContextMenuRadioItem>
<ContextMenuRadioItem value="green">
<ContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</ContextMenuItemIndicator>
Green
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
With complex items
You can add extra decorative elements in the Item parts, such as images.
<script setup lang="ts">
import { ContextMenuContent, ContextMenuItem, ContextMenuPortal, ContextMenuRoot, ContextMenuTrigger } from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent>
<ContextMenuItem>
<img src="…">
Adolfo Hess
</ContextMenuItem>
<ContextMenuItem>
<img src="…">
Miyah Myles
</ContextMenuItem>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
Constrain the content/sub-content size
You may want to constrain the width of the content (or sub-content) so that it matches the trigger (or sub-trigger) width. You may also want to constrain its height to not exceed the viewport.
We expose several CSS custom properties such as --akar-context-menu-trigger-width and --akar-context-menu-content-available-height to support this. Use them to constrain the content dimensions.
<script setup lang="ts">
import { ContextMenuContent, ContextMenuItem, ContextMenuPortal, ContextMenuRoot, ContextMenuTrigger } from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent class="ContextMenuContent">
…
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
/* styles.css */
.ContextMenuContent {
width: var(--akar-context-menu-trigger-width);
max-height: var(--akar-context-menu-content-available-height);
}
Origin-aware animations
We expose a CSS custom property --akar-context-menu-content-transform-origin. Use it to animate the content from its computed origin based on side, sideOffset, align, alignOffset and any collisions.
<script setup lang="ts">
import { ContextMenuContent, ContextMenuPortal, ContextMenuRoot, ContextMenuTrigger } from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent class="ContextMenuContent">
…
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
/* styles.css */
.ContextMenuContent {
transform-origin: var(--akar-context-menu-content-transform-origin);
animation: scaleIn 0.5s ease-out;
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
Collision-aware animations
We expose data-side and data-align attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.
<script setup lang="ts">
import { ContextMenuContent, ContextMenuPortal, ContextMenuRoot, ContextMenuTrigger } from 'akar'
</script>
<template>
<ContextMenuRoot>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent class="ContextMenuContent">
…
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
</template>
/* styles.css */
.ContextMenuContent {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.ContextMenuContent[data-side="top"] {
animation-name: slideUp;
}
.ContextMenuContent[data-side="bottom"] {
animation-name: slideDown;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Accessibility
Uses roving tabindex to manage focus movement among menu items.
Keyboard Interactions
| Key | Description |
|---|---|
| Space | Activates the focused item. |
| Enter | Activates the focused item. |
| ArrowDown | Moves focus to the next item. |
| ArrowUp | Moves focus to the previous item. |
| ArrowRight/ArrowLeft | When focus is on ContextMenu.SubTrigger, opens or closes the submenu depending on reading direction. |
| Esc | Closes the context menu |