Menu
A list of options that appears when a user interacts with a button.
You can explore the menu component in the following curated examples.
Menu with Combobox
Create a searchable dropdown menu by combining Menu and Combobox.
Nested Menu
Render a nested menu with submenus.
Menu with Links
Render a Menu with links as the Menu Items.
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/menu'
export const Basic = () => (
<Menu.Root>
<Menu.Trigger>
Open menu <Menu.Indicator>➡️</Menu.Indicator>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
import { Menu } from '@ark-ui/solid/menu'
export const Basic = () => (
<Menu.Root>
<Menu.Trigger>
Open menu <Menu.Indicator>➡️</Menu.Indicator>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
<script setup lang="ts">
import { Menu } from '@ark-ui/vue/menu'
</script>
<template>
<Menu.Root>
<Menu.Trigger>
Open menu
<Menu.Indicator>➡️</Menu.Indicator>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
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.
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/menu'
export const Group = () => (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>JS Frameworks</Menu.ItemGroupLabel>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.ItemGroup>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>CSS Frameworks</Menu.ItemGroupLabel>
<Menu.Item value="panda">Panda</Menu.Item>
<Menu.Item value="tailwind">Tailwind</Menu.Item>
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
import { Menu } from '@ark-ui/solid/menu'
export const Group = () => (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>JS Frameworks</Menu.ItemGroupLabel>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.ItemGroup>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>CSS Frameworks</Menu.ItemGroupLabel>
<Menu.Item value="panda">Panda</Menu.Item>
<Menu.Item value="tailwind">Tailwind</Menu.Item>
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
<script setup lang="ts">
import { Menu } from '@ark-ui/vue/menu'
</script>
<template>
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>JS Frameworks</Menu.ItemGroupLabel>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.ItemGroup>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>CSS Frameworks</Menu.ItemGroupLabel>
<Menu.Item value="panda">Panda</Menu.Item>
<Menu.Item value="tailwind">Tailwind</Menu.Item>
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
Separating menu items
To separate menu items, render the Menu.Separator
component.
import { Menu } from '@ark-ui/react/menu'
export const Separator = () => (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Separator />
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
import { Menu } from '@ark-ui/solid/menu'
export const Separator = () => (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Separator />
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
<script setup lang="ts">
import { Menu } from '@ark-ui/vue/menu'
</script>
<template>
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Separator />
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
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/menu'
export const Context = () => (
<Menu.Root>
<Menu.ContextTrigger>Right click me</Menu.ContextTrigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
import { Menu } from '@ark-ui/solid/menu'
export const Context = () => (
<Menu.Root>
<Menu.ContextTrigger>Right click me</Menu.ContextTrigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
<script setup lang="ts">
import { Menu } from '@ark-ui/vue/menu'
</script>
<template>
<Menu.Root>
<Menu.ContextTrigger>Right click me</Menu.ContextTrigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
Nested menu
To show a nested menu, render another Menu
component and use the Menu.TriggerItem
component to
open the submenu.
import { Menu } from '@ark-ui/react/menu'
import { Portal } from '@ark-ui/react/portal'
export const Nested = () => (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Root>
<Menu.TriggerItem>JS Frameworks</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
<Menu.Root>
<Menu.TriggerItem>CSS Frameworks</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="panda">Panda</Menu.Item>
<Menu.Item value="tailwind">Tailwind</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
import { Menu } from '@ark-ui/solid/menu'
import { Portal } from 'solid-js/web'
export const Nested = () => (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Root>
<Menu.TriggerItem>JS Frameworks</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
<Menu.Root>
<Menu.TriggerItem>CSS Frameworks</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="panda">Panda</Menu.Item>
<Menu.Item value="tailwind">Tailwind</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
<script setup lang="ts">
import { Menu } from '@ark-ui/vue/menu'
</script>
<template>
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Root>
<Menu.TriggerItem>JS Frameworks</Menu.TriggerItem>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
<Menu.Root>
<Menu.TriggerItem>CSS Frameworks</Menu.TriggerItem>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="panda">Panda</Menu.Item>
<Menu.Item value="tailwind">Tailwind</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
Checkbox
To add a checkbox to a menu item, use the Menu.Checkbox
component.
Radio Group
To group radio option items, use the Menu.RadioGroup
component.
import { Menu } from '@ark-ui/react/menu'
import { useState } from 'react'
export const RadioGroup = () => {
const [value, setValue] = useState('React')
return (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.RadioItemGroup value={value} onValueChange={(e) => setValue(e.value)}>
<Menu.ItemGroupLabel>JS Frameworks</Menu.ItemGroupLabel>
{['React', 'Solid', 'Vue'].map((framework) => (
<Menu.RadioItem key={framework} value={framework}>
<Menu.ItemIndicator>✅</Menu.ItemIndicator>
<Menu.ItemText>{framework}</Menu.ItemText>
</Menu.RadioItem>
))}
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
}
import { Menu } from '@ark-ui/solid/menu'
import { Index, createSignal } from 'solid-js'
export const RadioGroup = () => {
const [value, setValue] = createSignal('React')
return (
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.RadioItemGroup value={value()} onValueChange={(e) => setValue(e.value)}>
<Menu.ItemGroupLabel>JS Frameworks</Menu.ItemGroupLabel>
<Index each={['React', 'Solid', 'Svelte', 'Vue']}>
{(framework) => (
<Menu.RadioItem value={framework()} disabled={framework() === 'Svelte'}>
<Menu.ItemIndicator>✅</Menu.ItemIndicator>
<Menu.ItemText>{framework()}</Menu.ItemText>
</Menu.RadioItem>
)}
</Index>
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)
}
<script setup lang="ts">
import { Menu } from '@ark-ui/vue/menu'
import { ref } from 'vue'
const value = ref('React')
const items = ref(['React', 'Solid', 'Vue'])
</script>
<template>
<Menu.Root>
<Menu.Trigger>Open menu</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.RadioItemGroup v-model="value">
<Menu.ItemGroupLabel>JS Frameworks</Menu.ItemGroupLabel>
<Menu.RadioItem v-for="item in items" :key="item" :value="item">
<Menu.ItemIndicator>✅</Menu.ItemIndicator>
<Menu.ItemText>{{ item }}</Menu.ItemText>
</Menu.RadioItem>
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
Using the Root Provider
The RootProvider
component provides a context for the menu. It accepts the value of the useMenu
hook.
You can leverage it to access the component state and methods from outside the menu.
import { Menu, useMenu } from '@ark-ui/react/menu'
export const RootProvider = () => {
const menu = useMenu()
return (
<>
<button onClick={() => menu.api.setHighlightedValue('solid')}>Highlight Solid</button>
<Menu.RootProvider value={menu}>
<Menu.Trigger>
Open menu <Menu.Indicator>➡️</Menu.Indicator>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.RootProvider>
</>
)
}
import { Menu, useMenu } from '@ark-ui/solid/menu'
export const RootProvider = () => {
const menu = useMenu()
return (
<>
<button onClick={() => menu.api().setHighlightedValue('solid')}>Highlight Solid</button>
<Menu.Root>
<Menu.Trigger>
Open menu <Menu.Indicator>➡️</Menu.Indicator>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</>
)
}
<script setup lang="ts">
import { Menu, useMenu } from '@ark-ui/vue/menu'
const menu = useMenu()
</script>
<template>
<button @click="menu.api.value.setHighlightedValue('solid')">Highlight Solid</button>
<Menu.Root>
<Menu.Trigger>
Open menu
<Menu.Indicator>➡️</Menu.Indicator>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="react">React</Menu.Item>
<Menu.Item value="solid">Solid</Menu.Item>
<Menu.Item value="vue">Vue</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
</template>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Root
Prop | Default | Type |
---|---|---|
anchorPoint | Point The positioning point for the menu. Can be set by the context menu trigger or the button trigger. | |
aria-label | string The accessibility label for the menu | |
closeOnSelect | true | boolean Whether to close the menu when an option is selected |
composite | true | boolean Whether the menu is a composed with other composite widgets like a combobox or tabs |
defaultOpen | boolean The initial open state of the menu when it is first rendered. Use when you do not need to control its open state. | |
highlightedValue | string The value of the highlighted menu item. | |
id | string The unique identifier of the machine. | |
ids | Partial<{
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. | |
immediate | boolean Whether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | boolean Whether to enable lazy mounting |
loopFocus | false | boolean Whether to loop the keyboard navigation. |
onEscapeKeyDown | (event: KeyboardEvent) => void Function called when the escape key is pressed | |
onExitComplete | () => void Function called when the animation ends in the closed state | |
onFocusOutside | (event: FocusOutsideEvent) => void Function called when the focus is moved outside the component | |
onHighlightChange | (details: HighlightChangeDetails) => void Function called when the highlighted menu item changes. | |
onInteractOutside | (event: InteractOutsideEvent) => void Function called when an interaction happens outside the component | |
onOpenChange | (details: OpenChangeDetails) => void Function called when the menu opens or closes | |
onPointerDownOutside | (event: PointerDownOutsideEvent) => void Function called when the pointer is pressed down outside the component | |
onSelect | (details: SelectionDetails) => void Function called when a menu item is selected. | |
open | boolean Whether the menu is open | |
positioning | PositioningOptions The options used to dynamically position the menu | |
present | boolean Whether the node is present (controlled by the user) | |
typeahead | true | boolean Whether the pressing printable characters should trigger typeahead navigation |
unmountOnExit | false | boolean Whether to unmount on exit. |
Arrow
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ArrowTip
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
CheckboxItem
Prop | Default | Type |
---|---|---|
checked | boolean Whether the option is checked | |
value | string The value of the option | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
closeOnSelect | boolean Whether the menu should be closed when the option is selected. | |
disabled | boolean Whether the menu item is disabled | |
onCheckedChange | (checked: boolean) => void Function called when the option state is changed | |
valueText | string The textual value of the option. Used in typeahead navigation of the menu. If not provided, the text content of the menu item will be used. |
Content
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | menu |
[data-part] | content |
[data-state] | "open" | "closed" |
[data-placement] | The placement of the content |
ContextTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Indicator
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | menu |
[data-part] | indicator |
[data-state] | "open" | "closed" |
ItemGroupLabel
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ItemGroup
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ItemIndicator
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | menu |
[data-part] | item-indicator |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
[data-state] | "checked" | "unchecked" |
Item
Prop | Default | Type |
---|---|---|
value | string The unique value of the menu item option. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
closeOnSelect | boolean Whether the menu should be closed when the option is selected. | |
disabled | boolean Whether the menu item is disabled | |
valueText | string The textual value of the option. Used in typeahead navigation of the menu. If not provided, the text content of the menu item will be used. |
Data Attribute | Value |
---|---|
[data-scope] | menu |
[data-part] | item |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
[data-valuetext] | The human-readable value |
ItemText
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | menu |
[data-part] | item-text |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
[data-state] | "checked" | "unchecked" |
Positioner
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
RadioItemGroup
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
onValueChange | (e: ValueChangeDetails) => void | |
value | string |
RadioItem
Prop | Default | Type |
---|---|---|
value | string The value of the option | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
closeOnSelect | boolean Whether the menu should be closed when the option is selected. | |
disabled | boolean Whether the menu item is disabled | |
valueText | string The textual value of the option. Used in typeahead navigation of the menu. If not provided, the text content of the menu item will be used. |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseMenuReturn | |
immediate | boolean Whether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | boolean Whether to enable lazy mounting |
onExitComplete | () => void Function called when the animation ends in the closed state | |
present | boolean Whether the node is present (controlled by the user) | |
unmountOnExit | false | boolean Whether to unmount on exit. |
Separator
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
TriggerItem
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Trigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | menu |
[data-part] | trigger |
[data-placement] | The placement of the trigger |
[data-state] | "open" | "closed" |
Accessibility
Complies with the Menu WAI-ARIA design pattern.
Keyboard Support
Key | Description |
---|---|
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 menu and moves focus to the trigger |