<script setup lang="ts">
import {
AContextMenuContent,
AContextMenuGroup,
AContextMenuItem,
AContextMenuLabel,
AContextMenuPortal,
AContextMenuRoot,
AContextMenuSub,
AContextMenuSubContent,
AContextMenuSubTrigger,
AContextMenuTrigger,
} from 'akar';
const contentClasses = 'rounded-md bg-background flex flex-col min-w-32 ring ring-ring shadow-lg origin-$akar-context-menu-content-transform-origin overflow-hidden animate-in fade-in-80 data-[state=closed]:(animate-out fade-out-0 zoom-out-95) data-[state=open]:(animate-in fade-in-0 zoom-in-95) data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2';
const itemClasses = 'group text-sm color-text p-1.5 outline-none flex gap-1.5 w-full select-none transition-colors-280 items-start relative data-[highlighted]:color-text-highlighted data-[state=open]:color-text-highlighted before:(rounded-md content-empty transition-colors-280 inset-px absolute -z-1) data-[disabled]:(opacity-75 cursor-not-allowed) data-[highlighted]:before:bg-background-elevated/50 data-[state=open]:before:bg-background-elevated/50';
const leadingIconClasses = 'shrink-0 color-text-dimmed group-data-[highlighted]:color-text group-data-[state=open]:color-text transition-colors-280 size-5';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger
as-child
class="text-sm border border-border-accented rounded-md border-dashed flex w-72 aspect-video items-center justify-center"
>
<span> Right click here. </span>
</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent
:class="contentClasses"
:side-offset="5"
>
<div class="flex-1 relative overflow-y-auto scroll-py-1 divide-divide divide-y">
<AContextMenuGroup class="p-1 isolate">
<AContextMenuLabel class="text-sm color-text-highlighted font-semibold p-1.5 flex gap-1.5 w-full items-center">
<span class="align-middle rounded-full bg-background-elevated inline-flex shrink-0 size-5 select-none items-center justify-center">
<img
src="https://github.com/praburangki.png"
class="rounded-inherit size-full object-cover"
>
</span>
<span class="text-start flex flex-1 flex-col min-w-0">
My account
</span>
</AContextMenuLabel>
</AContextMenuGroup>
<AContextMenuGroup class="p-1 isolate">
<AContextMenuSub>
<AContextMenuSubTrigger
as="button"
:class="itemClasses"
>
<span class="text-start flex flex-1 flex-col min-w-0">
<span class="truncate">Appearance</span>
<span class="color-text-muted truncate">Change the appearance of the app</span>
</span>
<span class="ms-auto inline-flex gap-1.5 items-center"><span class="i-lucide:chevron-right shrink-0 size-5" /></span>
</AContextMenuSubTrigger>
<AContextMenuPortal>
<AContextMenuSubContent :class="contentClasses">
<div class="flex-1 relative overflow-y-auto scroll-py-1 divide-divide divide-y">
<AContextMenuGroup class="p-1 isolate">
<AContextMenuItem as-child>
<button :class="itemClasses">
<span
class="i-lucide:monitor"
:class="leadingIconClasses"
/>
<span class="text-start flex flex-1 flex-col min-w-0"><span class="truncate">System</span></span>
</button>
</AContextMenuItem>
<AContextMenuItem as-child>
<button :class="itemClasses">
<span
class="i-lucide:sun"
:class="leadingIconClasses"
/>
<span class="text-start flex flex-1 flex-col min-w-0"><span class="truncate">Light</span></span>
</button>
</AContextMenuItem>
<AContextMenuItem as-child>
<button :class="itemClasses">
<span
class="i-lucide:moon"
:class="leadingIconClasses"
/>
<span class="text-start flex flex-1 flex-col min-w-0"><span class="truncate">Dark</span></span>
</button>
</AContextMenuItem>
</AContextMenuGroup>
</div>
</AContextMenuSubContent>
</AContextMenuPortal>
</AContextMenuSub>
</AContextMenuGroup>
<AContextMenuGroup class="p-1 isolate">
<AContextMenuItem as-child>
<button :class="itemClasses">
Show Sidebar
</button>
</AContextMenuItem>
<AContextMenuItem as-child>
<button :class="itemClasses">
Show Toolbar
</button>
</AContextMenuItem>
<AContextMenuItem
as-child
disabled
>
<button :class="itemClasses">
Collapse Pinned Tabs
</button>
</AContextMenuItem>
</AContextMenuGroup>
</div>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
Import all parts and piece them together.
<script setup lang="ts">
import {
AContextMenuCheckboxItem,
AContextMenuContent,
AContextMenuGroup,
AContextMenuItem,
AContextMenuItemIndicator,
AContextMenuLabel,
AContextMenuPortal,
AContextMenuRadioGroup,
AContextMenuRadioItem,
AContextMenuRoot,
AContextMenuSeparator,
AContextMenuSub,
AContextMenuSubContent,
AContextMenuSubTrigger,
AContextMenuTrigger,
} from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger />
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuLabel />
<AContextMenuItem />
<AContextMenuGroup>
<AContextMenuItem />
</AContextMenuGroup>
<AContextMenuCheckboxItem>
<AContextMenuItemIndicator />
</AContextMenuCheckboxItem>
<AContextMenuRadioGroup>
<AContextMenuRadioItem>
<AContextMenuItemIndicator />
</AContextMenuRadioItem>
</AContextMenuRadioGroup>
<AContextMenuSub>
<AContextMenuSubTrigger />
<AContextMenuPortal>
<AContextMenuSubContent />
</AContextMenuPortal>
</AContextMenuSub>
<AContextMenuSeparator />
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
One benefit of using Akar is its flexibility and low-level control over the components. However, this also means that you may need to manually construct more complex UI elements by combining multiple Akar components together.
If you feel there's a lot of elements that needs to be constructed manually using Akar, consider using Pohon UI instead. It provides a higher-level abstraction over Akar components with pre-defined styles and behaviors that can help you build UIs faster.
Adheres to the Menu WAI-ARIA design pattern and uses roving tabindex to manage focus movement among menu items.
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 | Type |
|---|---|
update:open | [payload: boolean]Event handler called when the open state of the combobox changes. |
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' | APrimitiveAsTag | 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. |
| Attribute | Value |
|---|---|
[data-state] | 'open' | 'closed' |
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+) {@link https://vuejs.org/guide/built-ins/teleport.html#deferred-teleport} | |
disabled | booleanWhen Note that this will also restore the native context menu. | |
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 {@link https://vuejs.org/guide/built-ins/teleport.html#basic-usage} |
The component that pops out in an open context menu.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | 0 | numberAn offset in pixels from the |
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<Side, 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: { top: 20, left: 20 }. |
disableUpdateOnLayoutShift | 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. | |
hideWhenDetached | false | booleanWhether to hide the content when the trigger becomes fully occluded. |
loop | booleanWhen | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | 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 | booleanFlip to the opposite side when colliding with boundary. | |
sticky | 'partial' | 'always' | 'partial'The sticky behavior on the align axis. |
| Event | Type |
|---|---|
closeAutoFocus | [event: Event] |
escapeKeyDown | [event: KeyboardEvent] |
focusOutside | [event: FocusOutsideEvent] |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent] |
pointerDownOutside | [event: PointerDownOutsideEvent] |
| Attribute | Value |
|---|---|
[data-state] | 'open' | 'closed' |
[data-side] | 'left' | 'right' | 'bottom' | 'top' |
[data-align] | 'start' | 'end' | 'center' |
| Variable | Description |
|---|---|
--akar-context-menu-content-transform-origin | |
--akar-context-menu-content-available-width | |
--akar-context-menu-content-available-height | |
--akar-context-menu-trigger-width | |
--akar-context-menu-trigger-height |
An optional arrow element to render alongside a submenu. This can be used to help visually link the trigger item with the AContextMenu.Content. Must be rendered inside AContextMenu.Content.
| Prop | Default | Type |
|---|---|---|
as | 'svg' | APrimitiveAsTag | 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 | booleanWhen | |
width | 10 | numberThe width of the arrow in pixels. |
The component that contains the context menu items.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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 Note that this will also restore the native context menu. | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the |
| Event | Type |
|---|---|
select | [event: Event] |
| Attribute | Value |
|---|---|
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
Used to group multiple AContextMenu.Items.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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. |
Used to render a label. It won't be focusable using arrow keys.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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. |
An item that can be controlled and rendered like a checkbox.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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 Note that this will also restore the native context menu. | |
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 |
| Event | Type |
|---|---|
select | [event: Event] |
update:modelValue | [payload: boolean]Event handler called when the value changes. |
| Attribute | Value |
|---|---|
[data-state] | 'checked' | 'unchecked' | 'indeterminate' |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
Used to group multiple AContextMenu.RadioItems.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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 | stringThe controlled checked state of the item. Can be used as |
| Event | Type |
|---|---|
update:modelValue | [payload: string]Event handler called when the value changes. |
An item that can be controlled and rendered like a radio.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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 Note that this will also restore the native context menu. | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the | |
value* | stringThe unique value of the item. |
| Event | Type |
|---|---|
select | [event: Event] |
| Attribute | Value |
|---|---|
[data-state] | 'checked' | 'unchecked' | 'indeterminate' |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
Renders when the parent AContextMenu.CheckboxItem or AContextMenu.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' | APrimitiveAsTag | 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. |
| Attribute | Value |
|---|---|
[data-state] | 'checked' | 'unchecked' | 'indeterminate' |
Used to visually separate items in the context menu.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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. |
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 | boolean |
| Event | Type |
|---|---|
update:open | [payload: boolean]Event handler called when the open state of the combobox changes. |
| Slot | Type |
|---|---|
open | booleanThe controlled open state of the menu. Can be used as |
An item that opens a submenu. Must be rendered inside AContextMenu.Sub.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | 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 Note that this will also restore the native context menu. | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the |
| Attribute | Value |
|---|---|
[data-state] | 'open' | 'closed' |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
The component that pops out when a submenu is open. Must be rendered inside AContextMenu.Sub.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | numberAn offset in pixels from the | |
arrowPadding | 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. | |
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 | 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 | number | Partial<Record<Side, 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: { top: 20, left: 20 }. | |
disableUpdateOnLayoutShift | 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. | |
hideWhenDetached | booleanWhether to hide the content when the trigger becomes fully occluded. | |
loop | booleanWhen | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | 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 | booleanFlip to the opposite side when colliding with boundary. | |
sideOffset | numberThe distance in pixels from the trigger. | |
sticky | 'always' | 'partial'The sticky behavior on the align axis. | |
updatePositionStrategy | 'always' | 'optimized'Strategy to update the position of the floating element on every animation frame. |
| Event | Type |
|---|---|
closeAutoFocus | [event: Event] |
entryFocus | [event: Event] |
escapeKeyDown | [event: KeyboardEvent] |
focusOutside | [event: FocusOutsideEvent] |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent] |
openAutoFocus | [event: Event] |
pointerDownOutside | [event: PointerDownOutsideEvent] |
| Attribute | Value |
|---|---|
[data-state] | 'open' | 'closed' |
[data-side] | 'left' | 'right' | 'bottom' | 'top' |
[data-align] | 'start' | 'end' | 'center' |
| Variable | Description |
|---|---|
--akar-context-menu-content-transform-origin | The |
--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 |
You can create submenus by using AContextMenuSub in combination with its parts.
<script setup lang="ts">
import {
AContextMenuContent,
AContextMenuItem,
AContextMenuPortal,
AContextMenuRoot,
AContextMenuSeparator,
AContextMenuSub,
AContextMenuSubContent,
AContextMenuSubTrigger,
AContextMenuTrigger,
} from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuSeparator />
<AContextMenuSub>
<AContextMenuSubTrigger>Sub menu →</AContextMenuSubTrigger>
<AContextMenuPortal>
<AContextMenuSubContent>
<AContextMenuItem>Sub menu item</AContextMenuItem>
<AContextMenuItem>Sub menu item</AContextMenuItem>
<AContextMenuArrow />
</AContextMenuSubContent>
</AContextMenuPortal>
</AContextMenuSub>
<AContextMenuSeparator />
<AContextMenuItem>…</AContextMenuItem>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
You can add special styles to disabled items via the data-disabled attribute.
<script setup lang="ts">
import { AContextMenuContent, AContextMenuItem, AContextMenuPortal, AContextMenuRoot, AContextMenuTrigger } from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuItem
class="AContextMenuItem"
disabled
>
…
</AContextMenuItem>
<AContextMenuItem class="AContextMenuItem">
…
</AContextMenuItem>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
/* styles.css */
.AContextMenuItem[data-disabled] {
color: gainsboro;
}
Use the Separator part to add a separator between items.
<script setup lang="ts">
import {
AContextMenuContent,
AContextMenuItem,
AContextMenuPortal,
AContextMenuRoot,
AContextMenuSeparator,
AContextMenuTrigger,
} from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuSeparator />
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuSeparator />
<AContextMenuItem>…</AContextMenuItem>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
Use the Label part to help label a section.
<script setup lang="ts">
import {
AContextMenuContent,
AContextMenuItem,
AContextMenuLabel,
AContextMenuPortal,
AContextMenuRoot,
AContextMenuTrigger,
} from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuLabel>Label</AContextMenuLabel>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuItem>…</AContextMenuItem>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
Use the CheckboxItem part to add an item that can be checked.
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import {
AContextMenuCheckboxItem,
AContextMenuContent,
AContextMenuItem,
AContextMenuItemIndicator,
AContextMenuPortal,
AContextMenuRoot,
AContextMenuSeparator,
AContextMenuTrigger,
} from 'akar';
const checked = ref(true);
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuItem>…</AContextMenuItem>
<AContextMenuSeparator />
<AContextMenuCheckboxItem v-model="checked">
<AContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</AContextMenuItemIndicator>
Checkbox item
</AContextMenuCheckboxItem>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
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 {
AContextMenuCheckboxItem,
AContextMenuContent,
AContextMenuItem,
AContextMenuItemIndicator,
AContextMenuPortal,
AContextMenuRadioGroup,
AContextMenuRadioItem,
AContextMenuRoot,
AContextMenuSeparator,
AContextMenuTrigger,
} from 'akar';
const color = ref('blue');
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuRadioGroup v-model="color">
<AContextMenuRadioItem value="red">
<AContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</AContextMenuItemIndicator>
Red
</AContextMenuRadioItem>
<AContextMenuRadioItem value="blue">
<AContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</AContextMenuItemIndicator>
Blue
</AContextMenuRadioItem>
<AContextMenuRadioItem value="green">
<AContextMenuItemIndicator>
<Icon icon="radix-icons:check" />
</AContextMenuItemIndicator>
Green
</AContextMenuRadioItem>
</AContextMenuRadioGroup>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
You can add extra decorative elements in the Item parts, such as images.
<script setup lang="ts">
import { AContextMenuContent, AContextMenuItem, AContextMenuPortal, AContextMenuRoot, AContextMenuTrigger } from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent>
<AContextMenuItem>
<img src="…">
Adolfo Hess
</AContextMenuItem>
<AContextMenuItem>
<img src="…">
Miyah Myles
</AContextMenuItem>
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
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 { AContextMenuContent, AContextMenuItem, AContextMenuPortal, AContextMenuRoot, AContextMenuTrigger } from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent class="AContextMenuContent">
…
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
/* styles.css */
.AContextMenuContent {
width: var(--akar-context-menu-trigger-width);
max-height: var(--akar-context-menu-content-available-height);
}
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 { AContextMenuContent, AContextMenuPortal, AContextMenuRoot, AContextMenuTrigger } from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent class="AContextMenuContent">
…
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
/* styles.css */
.AContextMenuContent {
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);
}
}
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 { AContextMenuContent, AContextMenuPortal, AContextMenuRoot, AContextMenuTrigger } from 'akar';
</script>
<template>
<AContextMenuRoot>
<AContextMenuTrigger>…</AContextMenuTrigger>
<AContextMenuPortal>
<AContextMenuContent class="AContextMenuContent">
…
</AContextMenuContent>
</AContextMenuPortal>
</AContextMenuRoot>
</template>
/* styles.css */
.AContextMenuContent {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.AContextMenuContent[data-side='top'] {
animation-name: slideUp;
}
.AContextMenuContent[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);
}
}
Uses roving tabindex to manage focus movement among menu items.
| 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. |
ArrowRightArrowLeft | When focus is on |
Esc | Closes the context menu |