Menu
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Menu

Context menu modeled on modern mobile behavior, especially iOS.

Library

Copy

Share

Favorite

Delete

Installation

npx axiom add menu
pnpm dlx axiom add menu
yarn dlx axiom add menu
bun x axiom add menu

Also copies: portal, overlay, use-overlay-back-handler, which closes the menu on a back action. See navigation.

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>
		<PhotoThumbnail />
	</Menu.Trigger>
	<Menu.Content>
		<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>;

Props

PropTypeDefaultDescription
openbooleanControlled visibility.
defaultOpenbooleanfalseInitial visibility when the menu manages its own state.
onOpenChange(open: boolean) => voidCalled when the menu opens or closes.
childrenReactNodeTrigger and Content.
PropTypeDefaultDescription
action'longPress' | 'press''longPress'longPress for context menus on content, press for a “more” button.
previewboolean | ReactNodetrue with longPressLifts a copy of the pressed element above the backdrop. Pass a node to show it in the element’s place instead.
asChildbooleanfalseUses the child as the trigger.
accessibilityLabelstringLabel of the trigger, when its content has no text.
childrenReactNodeThe element that opens the menu.
styleStyleProp<ViewStyle>Extra styles for the trigger.
PropTypeDefaultDescription
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.
widthnumber250Menu 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.
childrenReactNodeItems, groups and separators.
styleStyleProp<ViewStyle>Extra styles for the menu surface.
PropTypeDefaultDescription
childrenstringThe label.
iconIconNameIcon on the right, iOS style.
subtitlestringSecond line under the label.
onPress() => voidCalled after the menu closes, so a navigation or a sheet doesn’t fight the close animation.
destructivebooleanfalseLabel and icon in feedback.error.
disabledbooleanfalseDims the item and ignores presses.
checkedbooleanShows a checkmark, for a menu that picks a sort order or a view.
keepOpenbooleanfalseRuns onPress right away without closing the menu, for a toggle inside the menu.
PartPropsDescription
GrouplabelGroups items under an optional small label.
SeparatorA thick gap between groups, like iOS.
Sublabel, icon, childrenAn 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).

Use cases

“More” button in an app bar

action="press" on an IconButton. No preview, the menu hangs from the button.

Q3 planning
Sort by

Date

Name

Rename

Delete

<Menu.Root>
	<Menu.Trigger action="press" 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>

Long press on a message

The message is lifted as a preview above the backdrop; quick reactions sit above it.

Found the bug.

❤️👍😂😮

It was a moth. Taped it in the logbook.

Reply

Copy

Delete

<Menu.Root>
	<Menu.Trigger preview>
		<MessageBubble message={message} />
	</Menu.Trigger>
	<Menu.Content>
		<Reactions onReact={react} />
		<Menu.Item icon="arrow-up" onPress={reply}>
			Reply
		</Menu.Item>
		<Menu.Item icon="copy" onPress={copy}>
			Copy
		</Menu.Item>
		<Menu.Item icon="trash" destructive onPress={remove}>
			Delete
		</Menu.Item>
	</Menu.Content>
</Menu.Root>

Row actions in a list

A long press on a row, as an alternative to swipe actions that is easier to discover.

Roadmap.pdf
Moodboard.png4.2 MB

Open with…

Move

Rename

<Menu.Root>
	<Menu.Trigger>
		<Item onPress={() => open(file)}>…</Item>
	</Menu.Trigger>
	<Menu.Content>
		<Menu.Sub label="Open with…">{apps.map(renderApp)}</Menu.Sub>
		<Menu.Item icon="archive" onPress={move}>
			Move
		</Menu.Item>
		<Menu.Item icon="edit" disabled={file.locked} onPress={rename}>
			Rename
		</Menu.Item>
	</Menu.Content>
</Menu.Root>