Menu
A menu that hangs from a button, modeled on iOS.
Installation
npx axiom add menupnpm dlx axiom add menuyarn dlx axiom add menubun x axiom add menuAlso copies: portal, overlay, use-overlay-back-handler, which closes the menu on a back action. See navigation. And overlay-stack: a menu opened from a BottomSheet takes the back action and the touches from the sheet, without scaling it.
Installs: react-native-reanimated, react-native-safe-area-context, react-native-worklets.
Requires the arrow-left, check and chevron-right icons in your icon registry. Positioning and the animation live in use-menu.ts. Colors come from menu.default.{default,pressed,destructive,disabled} in the component tokens.
Usage
import { Menu } from "@/components/ui/menu";
<Menu.Root>
<Menu.Trigger asChild>
<IconButton icon="more" accessibilityLabel="More options" />
</Menu.Trigger>
<Menu.Content align="end">
<Menu.Item icon="share" onPress={share}>
Share
</Menu.Item>
<Menu.Separator />
<Menu.Item icon="trash" destructive onPress={remove}>
Delete
</Menu.Item>
</Menu.Content>
</Menu.Root>;Menu.Trigger opens the menu on a press. For a menu opened by a long press on content, with the content lifted above the backdrop, use ContextMenu: it takes the same parts.
Props
Menu.Root
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled visibility. |
defaultOpen | boolean | false | Initial visibility when the menu manages its own state. |
onOpenChange | (open: boolean) => void | — | Called when the menu opens or closes. |
children | ReactNode | — | Trigger and Content. |
Menu.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
haptic | HapticKind | false | — | Haptic played when the menu opens. Off unless you pass a kind. |
asChild | boolean | false | Uses the child as the trigger. |
accessibilityLabel | string | — | Label of the trigger, when its content has no text. |
children | ReactNode | — | The element that opens the menu. |
style | StyleProp<ViewStyle> | — | Extra styles for the trigger. |
Menu.Content
| Prop | Type | Default | Description |
|---|---|---|---|
placement | 'auto' | 'top' | 'bottom' | 'auto' | Side of the trigger. auto picks the side with more room and keeps the menu inside the safe area. |
align | 'start' | 'center' | 'end' | 'start' | Horizontal alignment against the trigger. |
width | number | 250 | Menu width. |
overlay | 'dim' | 'none' | 'dim' | Backdrop behind the menu. Pressing it closes the menu, even when none. A blur needs expo-blur: add it in your copy if you want one. |
children | ReactNode | — | Items, groups and separators. |
style | StyleProp<ViewStyle> | — | Extra styles for the menu surface. |
Menu.Item
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | — | The label. |
icon | IconName | — | Icon on the right, iOS style. |
subtitle | string | — | Second line under the label. |
onPress | () => void | — | Called after the menu closes, so a navigation or a sheet doesn’t fight the close animation. |
destructive | boolean | false | Label and icon in feedback.error. |
disabled | boolean | false | Dims the item and ignores presses. |
checked | boolean | — | Shows a checkmark, for a menu that picks a sort order or a view. |
keepOpen | boolean | false | Runs onPress right away without closing the menu, for a toggle inside the menu. |
Menu.Group, Menu.Separator, Menu.Sub
| Part | Props | Description |
|---|---|---|
Group | label | Groups items under an optional small label. |
Separator | — | A thick gap between groups, like iOS. |
Sub | label, icon, children | An item that opens a nested menu in place. |
Menu.Sub replaces the items in place and adds a back row at the top. Menu.Item also takes keepOpen, to run onPress without closing the menu. The menu has accessibilityRole="menu", its items menuitem, and a back action closes it.
Not implemented yet: dragging from the trigger to an item, and haptics (the haptics library depends on the project, see the roadmap).
Haptics
A menu opened by a press plays nothing by default. Pass a kind to haptic on Menu.Trigger if your app wants one. ContextMenu plays medium on a long press. Nothing plays until your app connects a haptics library: see Haptics.
Use cases
“More” button in an app bar
An IconButton as the trigger. The menu hangs from the button.
<Menu.Root>
<Menu.Trigger asChild>
<IconButton icon="more" accessibilityLabel="More options" />
</Menu.Trigger>
<Menu.Content align="end">
<Menu.Group label="Sort by">
<Menu.Item checked={sort === "date"} onPress={() => setSort("date")}>
Date
</Menu.Item>
<Menu.Item checked={sort === "name"} onPress={() => setSort("name")}>
Name
</Menu.Item>
</Menu.Group>
<Menu.Separator />
<Menu.Item icon="edit" onPress={rename}>
Rename
</Menu.Item>
<Menu.Item icon="trash" destructive onPress={confirmDelete}>
Delete
</Menu.Item>
</Menu.Content>
</Menu.Root>Related
- BottomSheet
- ContextMenu, the same menu opened by a long press on content