Menu

A list of options that appears when a user interacts with a button.

Anatomy

To set up the menu correctly, you’ll need to understand its anatomy and how we name its parts.

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

Examples

Learn how to use the Menu component in your project. Let’s take a look at the most basic example:

import { Menu } from '@ark-ui/react'

const Basic = () => (
  <Menu.Root>
    <Menu.Trigger>Open menu</Menu.Trigger>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.Item id="search">Search</Menu.Item>
        <Menu.Item id="undo">Undo</Menu.Item>
        <Menu.Item id="delivery" disabled>
          Delivery
        </Menu.Item>
        <Menu.Item id="unlink">Unlink</Menu.Item>
      </Menu.Content>
    </Menu.Positioner>
  </Menu.Root>
)

Listening to item selection

Pass the onSelect prop to the Menu component to perform some custom logic when an item is selected. The callback is invoked with the id of the item.

import { Menu } from '@ark-ui/react'
import { useState } from 'react'

const Controlled = () => {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      <button onClick={() => setIsOpen(!isOpen)}>Trigger from the outside</button>
      <Menu.Root open={isOpen} onSelect={(id) => console.log(id)}>
        <Menu.Trigger>Open menu</Menu.Trigger>
        <Menu.Positioner>
          <Menu.Content>
            <Menu.Item id="search">Search</Menu.Item>
            <Menu.Item id="undo">Undo</Menu.Item>
            <Menu.Item id="delivery" disabled>
              Delivery
            </Menu.Item>
            <Menu.Item id="unlink">Unlink</Menu.Item>
          </Menu.Content>
        </Menu.Positioner>
      </Menu.Root>
    </>
  )
}

Grouping menu items

When the number of menu items gets much, it might be useful to group related menu items. To achieve this, render the Menu.ItemGroup component around the Menu.Item components. The Menu.ItemGroupLabel component can be used to add a label to the group.

import { Menu } from '@ark-ui/react'

const Group = () => (
  <Menu.Root>
    <Menu.Trigger>Open menu</Menu.Trigger>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.ItemGroup id="group-1">
          <Menu.ItemGroupLabel htmlFor="group-1">Group 1</Menu.ItemGroupLabel>
          <Menu.Item id="share">Share...</Menu.Item>
          <Menu.Item id="move">Move...</Menu.Item>
        </Menu.ItemGroup>
        <Menu.ItemGroup id="group-2">
          <Menu.ItemGroupLabel htmlFor="group-2">Group 2</Menu.ItemGroupLabel>
          <Menu.Item id="rename">Rename...</Menu.Item>
          <Menu.Item id="delete">Delete...</Menu.Item>
        </Menu.ItemGroup>
      </Menu.Content>
    </Menu.Positioner>
  </Menu.Root>
)

Separating menu items

To separate menu items, render the Menu.Separator component.

import { Menu } from '@ark-ui/react'

const Separator = () => (
  <Menu.Root>
    <Menu.Trigger>Open menu</Menu.Trigger>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.Item id="search">Search</Menu.Item>
        <Menu.Item id="undo">Undo</Menu.Item>
        <Menu.Separator />
        <Menu.Item id="delivery" disabled>
          Delivery
        </Menu.Item>
        <Menu.Item id="unlink">Unlink</Menu.Item>
      </Menu.Content>
    </Menu.Positioner>
  </Menu.Root>
)

Context menu

To show the menu when a trigger element is right-clicked, use the Menu.ContextTrigger component.

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

import { Menu } from '@ark-ui/react'

const ContextMenu = () => (
  <Menu.Root>
    <Menu.ContextTrigger>
      <div style={{ width: '100%', height: '20rem', border: '1px solid lightgray' }}>
        Some content
      </div>
    </Menu.ContextTrigger>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.Item id="search">Search</Menu.Item>
        <Menu.Item id="undo">Undo</Menu.Item>
        <Menu.Item id="delivery" disabled>
          Delivery
        </Menu.Item>
        <Menu.Item id="unlink">Unlink</Menu.Item>
      </Menu.Content>
    </Menu.Positioner>
  </Menu.Root>
)

Nested menu

To show a nested menu, render another Menu component and use the Menu.TriggerItem component to open the submenu.

import { Menu, Portal } from '@ark-ui/react'

const SubMenu = () => (
  <Menu.Root>
    <Menu.Trigger>Open menu</Menu.Trigger>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.Item id="new-tab">New Tab...</Menu.Item>
        <Menu.Item id="new-win">New Window...</Menu.Item>
        <Menu.Separator />
        <Menu>
          <Menu.TriggerItem>Share &gt;</Menu.TriggerItem>
          <Portal>
            <Menu.Positioner>
              <Menu.Content>
                <Menu.Item id="twitter">Twitter</Menu.Item>
                <Menu.Item id="message">Message</Menu.Item>
              </Menu.Content>
            </Menu.Positioner>
          </Portal>
        </Menu>
      </Menu.Content>
    </Menu.Positioner>
  </Menu.Root>
)

Checkbox and Radio option items

To show a checkbox or radio option item, use the Menu.OptionItem component. Depending on the type prop, the item will be rendered as a checkbox or radio option item. The name prop is used to group the items together, and the value prop is used to identify the item.

To manage the state of the option items pass the value and onValueChange props to the Menu component.

import { Menu } from '@ark-ui/react'
import { useState } from 'react'

const Options = () => {
  const [value, setValue] = useState<Record<string, string | string[]>>({
    framework: '',
    libraries: [],
  })
  return (
    <Menu.Root
      value={value}
      onValueChange={(data) => {
        setValue((prev) => ({
          ...prev,
          [data.name]: data.value,
        }))
      }}
    >
      <Menu.Trigger>Open menu</Menu.Trigger>
      <Menu.Positioner>
        <Menu.Content>
          <Menu.ItemGroup id="radio-group">
            <Menu.ItemGroupLabel htmlFor="radio-group">Radio Group</Menu.ItemGroupLabel>
            <Menu.OptionItem name="framework" type="radio" value="react">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} React</>}
            </Menu.OptionItem>
            <Menu.OptionItem name="framework" type="radio" value="solid">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} Solid</>}
            </Menu.OptionItem>
            <Menu.OptionItem name="framework" type="radio" value="vue">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} Vue</>}
            </Menu.OptionItem>
          </Menu.ItemGroup>
          <Menu.ItemGroup id="checkbox-group">
            <Menu.ItemGroupLabel htmlFor="checkbox-group">Checkbox Group</Menu.ItemGroupLabel>
            <Menu.OptionItem name="libraries" type="checkbox" value="zag-js">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} zag-js</>}
            </Menu.OptionItem>
            <Menu.OptionItem name="libraries" type="checkbox" value="ark">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} ark</>}
            </Menu.OptionItem>
            <Menu.OptionItem name="libraries" type="checkbox" value="panda">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} panda</>}
            </Menu.OptionItem>
            <Menu.OptionItem name="libraries" type="checkbox" value="chakra">
              {({ isChecked }) => <>{isChecked ? '✅' : ''} chakra</>}
            </Menu.OptionItem>
          </Menu.ItemGroup>
        </Menu.Content>
      </Menu.Positioner>
    </Menu.Root>
  )
}

API Reference

Root

PropTypeDefault
anchorPoint
Point
aria-label
string
closeOnSelect
boolean
dir
'ltr' | 'rtl'"ltr"
getRootNode
() => Node | ShadowRoot | Document
highlightedId
string
id
string
ids
Partial<{ trigger: string contextTrigger: string content: string label(id: string): string group(id: string): string positioner: string arrow: string }>
lazyMount
booleanfalse
loop
boolean
onExitComplete
() => void
onFocusOutside
(event: FocusOutsideEvent) => void
onInteractOutside
(event: InteractOutsideEvent) => void
onOpenChange
(details: OpenChangeDetails) => void
onPointerDownOutside
(event: PointerDownOutsideEvent) => void
onSelect
(details: SelectionDetails) => void
onValueChange
(details: ValueChangeDetails) => void
open
boolean
positioning
PositioningOptions
present
boolean
unmountOnExit
booleanfalse
value
Record<string, string | string[]>

Item

PropTypeDefault
id
string
asChild
boolean
closeOnSelect
boolean
disabled
boolean
valueText
string

Arrow

PropTypeDefault
asChild
boolean

Content

PropTypeDefault
asChild
boolean

Trigger

PropTypeDefault
asChild
boolean

ArrowTip

PropTypeDefault
asChild
boolean

ItemGroup

PropTypeDefault
id
string
asChild
boolean

Separator

PropTypeDefault
asChild
boolean

OptionItem

PropTypeDefault
name
string
type
'checkbox' | 'radio'
value
string
asChild
boolean
closeOnSelect
boolean
disabled
boolean
onCheckedChange
(checked: boolean) => void
valueText
string

Positioner

PropTypeDefault
asChild
boolean

TriggerItem

PropTypeDefault
asChild
boolean

ContextTrigger

PropTypeDefault
asChild
boolean

ItemGroupLabel

PropTypeDefault
htmlFor
string
asChild
boolean