Skip to main content

An accessible dropdown and context menu that is used to display a list of actions or options that a user can choose when a trigger element is right-clicked or long pressed.

Loading...

Features

  • Supports items, labels, groups of items
  • Focus is fully managed using aria-activedescendant pattern
  • Typeahead to allow focusing items by typing text
  • Keyboard navigation support including arrow keys, home/end, page up/down

Installation

Install the menu package:

npm install @zag-js/menu @zag-js/vue # or yarn add @zag-js/menu @zag-js/vue

Anatomy

Check the menu anatomy and part names.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

Import the menu package:

import * as menu from "@zag-js/menu"

The menu package exports two key functions:

  • machine - Behavior logic for the menu.
  • connect - Maps behavior to JSX props and event handlers.

Pass a unique id to useMachine so generated element ids stay predictable.

Then use the framework integration helpers:

To show the menu when a trigger element is right-clicked, use api.getContextTriggerProps().

Context menus also open during a long-press of roughly 700ms when the pointer is pen or touch.

<script setup> import * as menu from "@zag-js/menu" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const service = useMachine(menu.machine, { id: "1", "aria-label": "File" }) const api = computed(() => menu.connect(service, normalizeProps)) </script> <template> <div ref="ref"> <button v-bind="api.getContextTriggerProps()"> <div :style="{ border: 'solid 1px red' }">Open context menu</div> </button> <div v-bind="api.getPositionerProps()"> <ul v-bind="api.getContentProps()"> <li v-bind="api.getItemProps({ value: 'edit' })">Edit</li> <li v-bind="api.getItemProps({ value: 'duplicate' })">Duplicate</li> <li v-bind="api.getItemProps({ value: 'delete' })">Delete</li> <li v-bind="api.getItemProps({ value: 'export' })">Export...</li> </ul> </div> </div> </template>

Default open state

Use defaultOpen for an uncontrolled initial state.

const service = useMachine(menu.machine, { defaultOpen: true, })

Controlling open state

Use open and onOpenChange to control the open state.

const service = useMachine(menu.machine, { open, onOpenChange(details) { // details => { open: boolean } setOpen(details.open) }, })

Listening for highlighted items

Use onHighlightChange to react when keyboard or pointer highlight changes.

const service = useMachine(menu.machine, { onHighlightChange(details) { // details => { highlightedValue: string | null } console.log(details.highlightedValue) }, })

Listening for item selection

Use onSelect to react when an item is selected.

const service = useMachine(menu.machine, { onSelect(details) { // details => { value: string } console.log(details.value) }, })

Positioning the menu

Use positioning to configure menu placement.

const service = useMachine(menu.machine, { positioning: { placement: "right-start" }, })

Keeping the menu open after selection

Set closeOnSelect to false to keep the menu open after selecting an item.

const service = useMachine(menu.machine, { closeOnSelect: false, })

Styling guide

Each menu part includes a data-part attribute you can target in CSS.

Highlighted item state

When an item is highlighted, via keyboard navigation or pointer, it is given a data-highlighted attribute.

[data-part="item"][data-highlighted] { /* styles for highlighted state */ } [data-part="item"][data-type="radio|checkbox"][data-highlighted] { /* styles for highlighted state */ }

Disabled item state

When an item or an option item is disabled, it is given a data-disabled attribute.

[data-part="item"][data-disabled] { /* styles for disabled state */ } [data-part="item"][data-type="radio|checkbox"][data-disabled] { /* styles for disabled state */ }

Using arrows

When using arrows within the menu, you can style it using CSS variables.

[data-part="arrow"] { --arrow-size: 20px; --arrow-background: red; }

Checked option item state

When an option item is checked, it is given a data-state attribute.

[data-part="item"][data-type="radio|checkbox"][data-state="checked"] { /* styles for checked state */ }

Methods and Properties

Machine Context

The menu machine exposes the following context properties:

  • idsPartial<{ trigger: string; contextTrigger: string; content: string; groupLabel: (id: string) => string; group: (id: string) => string; positioner: string; arrow: string; }>The ids of the elements in the menu. Useful for composition.
  • defaultHighlightedValuestringThe initial highlighted value of the menu item when rendered. Use when you don't need to control the highlighted value of the menu item.
  • highlightedValuestringThe controlled highlighted value of the menu item.
  • onHighlightChange(details: HighlightChangeDetails) => voidFunction called when the highlighted menu item changes.
  • onSelect(details: SelectionDetails) => voidFunction called when a menu item is selected.
  • anchorPointPointThe positioning point for the menu. Can be set by the context menu trigger or the button trigger.
  • loopFocusbooleanWhether to loop the keyboard navigation.
  • positioningPositioningOptionsThe options used to dynamically position the menu
  • closeOnSelectbooleanWhether to close the menu when an option is selected
  • aria-labelstringThe accessibility label for the menu
  • openbooleanThe controlled open state of the menu
  • onOpenChange(details: OpenChangeDetails) => voidFunction called when the menu opens or closes
  • defaultOpenbooleanThe initial open state of the menu when rendered. Use when you don't need to control the open state of the menu.
  • typeaheadbooleanWhether the pressing printable characters should trigger typeahead navigation
  • compositebooleanWhether the menu is a composed with other composite widgets like a combobox or tabs
  • navigate(details: NavigateDetails) => voidFunction to navigate to the selected item if it's an anchor element
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
  • onEscapeKeyDown(event: KeyboardEvent) => voidFunction called when the escape key is pressed
  • onRequestDismiss(event: LayerDismissEvent) => voidFunction called when this layer is closed due to a parent layer being closed
  • onPointerDownOutside(event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component
  • onFocusOutside(event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component
  • onInteractOutside(event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component

Machine API

The menu api exposes the following methods:

  • openbooleanWhether the menu is open
  • setOpen(open: boolean) => voidFunction to open or close the menu
  • highlightedValuestringThe id of the currently highlighted menuitem
  • setHighlightedValue(value: string) => voidFunction to set the highlighted menuitem
  • setParent(parent: ParentMenuService) => voidFunction to register a parent menu. This is used for submenus
  • setChild(child: ChildMenuService) => voidFunction to register a child menu. This is used for submenus
  • reposition(options?: Partial<PositioningOptions>) => voidFunction to reposition the popover
  • getOptionItemState(props: OptionItemProps) => OptionItemStateReturns the state of the option item
  • getItemState(props: ItemProps) => ItemStateReturns the state of the menu item
  • addItemListener(props: ItemListenerProps) => VoidFunctionSetup the custom event listener for item selection event

Data Attributes

ContextTrigger
data-scope
menu
data-part
context-trigger
data-state
"open" | "closed"
Trigger
data-scope
menu
data-part
trigger
data-placement
The placement of the trigger
data-state
"open" | "closed"
Indicator
data-scope
menu
data-part
indicator
data-state
"open" | "closed"
Content
data-scope
menu
data-part
content
data-state
"open" | "closed"
data-nested
menu
data-has-nested
menu
data-placement
The placement of the content
Item
data-scope
menu
data-part
item
data-disabled
Present when disabled
data-highlighted
Present when highlighted
data-value
The value of the item
data-valuetext
The human-readable value
OptionItem
data-scope
menu
data-part
option-item
data-type
The type of the item
data-value
The value of the item
data-state
"checked" | "unchecked"
data-disabled
Present when disabled
data-highlighted
Present when highlighted
data-valuetext
The human-readable value
ItemIndicator
data-scope
menu
data-part
item-indicator
data-disabled
Present when disabled
data-highlighted
Present when highlighted
data-state
"checked"
ItemText
data-scope
menu
data-part
item-text
data-disabled
Present when disabled
data-highlighted
Present when highlighted
data-state
"checked"

CSS Variables

Arrow
--arrow-size
The size of the arrow
--arrow-size-half
Half the size of the arrow
--arrow-background
Use this variable to style the arrow background
--arrow-offset
The offset position of the arrow
Positioner
--reference-width
The width of the reference element
--reference-height
The height of the root
--available-width
The available width in viewport
--available-height
The available height in viewport
--x
The x position for transform
--y
The y position for transform
--z-index
The z-index value
--transform-origin
The transform origin for animations
Content
--layer-index
The index of the dismissable in the layer stack
--nested-layer-count
The number of nested menus
Backdrop
--layer-index
The index of the dismissable in the layer stack

Accessibility

Uses aria-activedescendant pattern to manage focus movement among menu items.

Keyboard Interactions

  • Space
    Activates/Selects the highlighted item
  • Enter
    Activates/Selects the highlighted item
  • ArrowDown
    Highlights the next item in the menu
  • ArrowUp
    Highlights the previous item in the menu
  • ArrowRightArrowLeft
    When focus is on trigger, opens or closes the submenu depending on reading direction.
  • Esc
    Closes the context menu
Edit this page on GitHub