code/tamagui.dev/data/docs/components/context-menu/2.0.0.mdx
<Highlights features={[ 'Full keyboard navigation.', 'Supports items, icons, images, checkboxes, groups, and more.', 'Supports submenus.', 'Supports modal and non-modal modes.', 'Supports native menus on native platforms.', 'Customizable alignment, offsets, and positioning.', 'Support for native iOS and Android icons.', 'Supports previews on iOS.', ]} />
ContextMenu displays a menu triggered by right-click on web or long-press on mobile. It supports submenus, native platform menus with iOS previews, and automatically stacks above other content.
ContextMenu is already installed in tamagui, or you can install it independently:
yarn add @tamagui/context-menu
If you want to use native menus, add these dependencies:
yarn add @react-native-menu/menu
yarn add react-native-ios-context-menu
yarn add react-native-ios-utilities
yarn add zeego
yarn add sf-symbols-typescript
Then add the setup import at your app entry point:
import '@tamagui/native/setup-zeego'
Import all parts and piece them together.
import { ContextMenu } from 'tamagui' // or '@tamagui/context-menu'
export default () => (
<ContextMenu>
<ContextMenu.Trigger asChild>
<YStack>
<Text>Right Click or longPress</Text>
</YStack>
</ContextMenu.Trigger>
<ContextMenu.Portal zIndex={100}>
<ContextMenu.Content>
<ContextMenu.Item>
<ContextMenu.ItemTitle>About Notes</ContextMenu.ItemTitle>
</ContextMenu.Item>
<ContextMenu.Item>
<ContextMenu.ItemTitle>Settings</ContextMenu.ItemTitle>
</ContextMenu.Item>
<ContextMenu.Item textValue="Calendar">
<ContextMenu.ItemTitle>
<Text>Calendar</Text>
</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon>
<Calendar color="gray" size="$1" />
</ContextMenu.ItemIcon>
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Sub>
<ContextMenu.SubTrigger>
<ContextMenu.ItemTitle>Actions</ContextMenu.ItemTitle>
</ContextMenu.SubTrigger>
<ContextMenu.Portal zIndex={200}>
<ContextMenu.SubContent>
<ContextMenu.Label fontSize={'$1'}>Note settings</ContextMenu.Label>
<ContextMenu.Item onSelect={onSelect} key="create-note">
<ContextMenu.ItemTitle>Create note</ContextMenu.ItemTitle>
</ContextMenu.Item>
<ContextMenu.Item onSelect={onSelect} key="delete-all">
<ContextMenu.ItemTitle>Delete all notes</ContextMenu.ItemTitle>
</ContextMenu.Item>
<ContextMenu.Item onSelect={onSelect} key="sync-all">
<ContextMenu.ItemTitle>Sync notes</ContextMenu.ItemTitle>
</ContextMenu.Item>
</ContextMenu.SubContent>
</ContextMenu.Portal>
</ContextMenu.Sub>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
)
Contains every component for the ContextMenu.
<PropsTable
data={[
{
name: 'children',
type: 'React.ReactNode',
required: true,
description: 'Menu parts: Trigger, Portal, Content, Items, etc.',
},
{
name: 'placement',
type: 'Placement',
required: false,
description: Where the menu appears relative to the trigger. Options: 'top' | 'right' | 'bottom' | 'left' with optional '-start' | '-end' alignment.,
},
{
name: 'open',
type: 'boolean',
required: false,
description: 'Controlled open state for the menu.',
},
{
name: 'defaultOpen',
type: 'boolean',
required: false,
description: 'Initial open state when uncontrolled.',
},
{
name: 'onOpenChange',
type: '(open: boolean, event?: { preventDefault: () => void }) => void',
required: false,
description: 'Called when the menu opens or closes. Call event.preventDefault() to cancel and allow the native context menu to show instead.',
},
{
name: 'onOpenWillChange',
type: '(open: boolean) => void',
required: false,
platform: 'ios',
description: 'Called before the open/close animation begins.',
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description:
'When true, traps focus inside the menu and blocks outside scroll/interactions.',
},
{
name: 'stayInFrame',
type: 'ShiftProps | boolean',
required: false,
default: '{ padding: 10 }',
description: 'Shifts the menu horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).',
},
{
name: 'allowFlip',
type: 'FlipProps | boolean',
required: false,
description: 'Flips the menu to the opposite side if there is insufficient space.',
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: 'Distance between the menu and its trigger.',
},
{
name: 'unstyled',
required: false,
type: 'boolean',
description: 'Removes all default Tamagui styles.',
},
]}
/>
You can call event.preventDefault() in onOpenChange to prevent the Tamagui menu from opening and allow the native browser context menu to appear instead:
<ContextMenu
onOpenChange={(open, event) => {
if (someCondition) {
// prevent Tamagui menu, let native context menu show
event?.preventDefault()
}
}}
>
</ContextMenu>
This is necessary for the ContextMenu.
<PropsTable data={[ { name: 'zIndex', required: false, type: 'number', description: 'Stacking order of the portal layer.', }, { name: 'children', type: 'React.ReactNode', required: true, description: 'Content to render inside the portal.', }, { name: 'forceMount', type: 'true', required: false, description: 'Forces the portal to stay mounted, useful for controlling animations.', }, ]} />
The ContextMenu will only be triggered when the user right-clicks or long-presses within the Trigger area.
<PropsTable data={[ { name: 'action', required: false, type: 'press|longPress', default: 'longPress', platform: 'android/ios', description: "Works with the native prop and accepts 'press' or 'longPress'. The default is 'longPress' for ContextMenu.", }, ]} />
Contains the content of the ContextMenu.
<PropsTable data={[ { name: 'children', type: 'React.ReactNode', required: true, description: 'Menu items, groups, labels, separators, and submenus.', }, { name: 'loop', type: 'boolean', required: false, default: 'false', description: 'Whether keyboard navigation wraps from last to first item.', }, { name: 'forceMount', type: 'true', required: false, description: 'Forces the content to stay mounted, useful for controlling animations.', }, { name: 'onCloseAutoFocus', type: '(event: Event) => void', required: false, description: 'Called when focus returns to the trigger after closing.', }, { name: 'onEscapeKeyDown', type: '(event: KeyboardEvent) => void', required: false, description: 'Called when the escape key is pressed. Can be prevented.', }, { name: 'onPointerDownOutside', type: '(event: PointerEvent) => void', required: false, description: 'Called when a pointer event occurs outside the content.', }, { name: 'onInteractOutside', type: '(event: Event) => void', required: false, description: 'Called when any interaction occurs outside the content.', }, ]} />
A selectable menu item that triggers an action when selected.
<PropsTable data={[ { name: 'key', type: 'string', required: true, description: 'Unique identifier for the item.', }, { name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Prevents interaction and dims the item.', }, { name: 'destructive', type: 'boolean', required: false, platform: 'ios/android', description: 'Renders the item in red on iOS to indicate a dangerous action (e.g. delete). No effect on web.', }, { name: 'hidden', type: 'boolean', required: false, description: 'Hides the item from the menu.', }, { name: 'onSelect', type: '(event?: Event) => void', required: false, description: 'Called when the item is selected via click or keyboard.', }, { name: 'onFocus', type: '() => void', required: false, description: 'Called when the item receives focus.', }, { name: 'onBlur', type: '() => void', required: false, description: 'Called when the item loses focus.', }, { name: 'textValue', type: 'string', required: false, platform: 'ios/android', description: 'Text used for typeahead and native menus. Required when ItemTitle contains a React node instead of a string.', }, ]} />
Renders the title of the menu item.
<PropsTable
data={[
{
name: 'children',
type: 'string | React.ReactNode',
required: true,
description: 'The title text or element to display.',
},
]}
/>
<Notice>
You can directly pass a text node to the ItemTitle. However, if you use a nested
React node like <Text>, you need to pass textValue to the <Item> so that it
works with native menus.
</Notice>
A component to render an icon. For non-native menus, you can pass an icon
component. For native menus, you can pass platform-specific icons to the
android/ios props.
On iOS, it renders the native SF Symbols icons.
<PropsTable data={[ { name: 'children', type: 'React.ReactNode', required: false, description: 'Fallback icon for web when native icons are not available.', }, { name: 'ios', type: 'object', required: false, platform: 'ios', description: 'SF Symbol configuration: name, weight, scale, hierarchicalColor, paletteColors.', }, { name: 'android', type: 'object', required: false, platform: 'android', description: 'Android resource drawable name.', }, ]} />
<ContextMenu.ItemIcon
ios={{
name: '0.circle.fill', // required
pointSize: 5,
weight: 'semibold',
scale: 'medium',
// can also be a color string. Requires iOS 15+
hierarchicalColor: {
dark: 'blue',
light: 'green',
},
// alternative to hierarchical color. Requires iOS 15+
paletteColors: [
{
dark: 'blue',
light: 'green',
},
],
}}
>
<CircleIcon />
</ContextMenu.ItemIcon>
A component to render an item image. For native menus, it only works on iOS. It
takes the same properties as @tamagui/image.
A component to render a subtitle for the menu item. For native menus, it only works on iOS.
<PropsTable data={[ { name: 'children', type: 'string', required: true, description: 'The subtitle text to display below the title.', }, ]} />
A component that groups multiple menu items together.
<PropsTable data={[ { name: 'children', type: 'React.ReactNode', required: true, description: 'Menu items to group together.', }, ]} />
A menu item with a checkbox that can be toggled on/off.
<PropsTable data={[ { name: 'key', type: 'string', required: true, description: 'Unique identifier for the checkbox item.', }, { name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Prevents interaction and dims the item.', }, { name: 'destructive', type: 'boolean', required: false, platform: 'ios/android', description: 'Renders the item in red on iOS to indicate a dangerous action. No effect on web.', }, { name: 'hidden', type: 'boolean', required: false, description: 'Hides the item from the menu.', }, { name: 'onFocus', type: '() => void', required: false, description: 'Called when the item receives focus.', }, { name: 'onBlur', type: '() => void', required: false, description: 'Called when the item loses focus.', }, { name: 'textValue', type: 'string', required: false, platform: 'ios/android', description: 'Text for native menus. Required when ItemTitle contains a React node.', }, { name: 'value', type: "'on' | 'off' | 'mixed'", required: false, platform: 'ios/android', description: 'Controlled checked state for native menus.', }, { name: 'onValueChange', type: '(state, prevState) => void', required: false, platform: 'ios/android', description: 'Called when checked state changes on native menus.', }, { name: 'checked', type: 'boolean', required: false, description: 'Controlled checked state for web menus.', }, { name: 'onCheckedChange', type: '(checked: boolean) => void', required: false, description: 'Called when checked state changes on web.', }, ]} />
Use inside CheckboxItem or RadioItem to indicate when an item is checked.
This allows you to conditionally render a checkmark.
<ContextMenu.ItemIndicator>
<CheckmarkIcon />
</ContextMenu.ItemIndicator>
<PropsTable data={[ { name: 'children', type: 'React.ReactNode', required: false, description: 'Custom checkmark icon. Only works on web.', }, { name: 'forceMount', type: 'true', required: false, description: 'Forces the indicator to stay mounted for animation control.', }, ]} />
Renders a non-focusable label for a group of items. On native menus, only one label is supported per menu and submenu.
<PropsTable data={[ { name: 'children', type: 'string', required: true, description: 'The label text.', }, { name: 'textValue', type: 'string', required: false, platform: 'ios/android', description: 'Text for native menus when children is a React node.', }, ]} />
Renders an arrow pointing to the trigger.
<PropsTable data={[ { name: 'size', type: 'number | SizeToken', required: false, description: 'Width and height of the arrow.', }, { name: 'unstyled', type: 'boolean', required: false, description: 'Removes default arrow styles.', }, ]} />
Renders a visual divider between menu items. Web only.
A container for nested sub-menu components.
<PropsTable data={[ { name: 'children', type: 'React.ReactNode', required: true, description: 'SubTrigger, Portal, and SubContent components.', }, { name: 'open', type: 'boolean', required: false, description: 'Controlled open state for the sub-menu.', }, { name: 'onOpenChange', type: '(open: boolean) => void', required: false, description: 'Called when the sub-menu opens or closes.', }, ]} />
Renders the content of a sub-menu. Same props as Content, excluding side and
align.
A menu item that opens a sub-menu on hover/focus. Accepts the same props as
Item.
When the ContextMenu is visible, this renders a custom preview component. Only works with native iOS menus.
<Notice>Should be rendered as a child of ContextMenu.Content.</Notice>
<PropsTable data={[ { name: 'children', type: 'React.ReactNode | (() => React.ReactNode)', required: true, platform: 'ios', description: 'The preview content to render. Can be a function for lazy rendering.', }, { name: 'size', type: '{ width?: number, height?: number }', required: false, platform: 'ios', description: 'Dimensions of the preview.', }, { name: 'onPress', type: '() => void', required: false, platform: 'ios', description: 'Called when the preview is pressed.', }, { name: 'backgroundColor', type: 'string | { dark: string, light: string }', required: false, platform: 'ios', description: 'Background color of the preview.', }, { name: 'borderRadius', type: 'number', required: false, platform: 'ios', description: 'Border radius of the preview.', }, { name: 'preferredCommitStyle', type: "'pop' | 'dismiss'", required: false, default: "'dismiss'", platform: 'ios', description: "Exit transition when preview is tapped. Use 'pop' when navigating to another screen.", }, ]} />
<ContextMenu.Preview
// optional props:
preferredCommitStyle="pop" // or "dismiss"
backgroundColor={{
// or a color string directly
dark: 'black',
light: 'white',
}}
>
{() => <Preview />}
</ContextMenu.Preview>