Back to Tamagui

Context Menu

code/tamagui.dev/data/docs/components/context-menu/2.0.0.mdx

1.144.418.2 KB
Original Source
<HeroContainer> <ContextMenuDemo /> </HeroContainer>
tsx

<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.

Installation

ContextMenu is already installed in tamagui, or you can install it independently:

bash
yarn add @tamagui/context-menu

If you want to use native menus, add these dependencies:

sh
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:

tsx
import '@tamagui/native/setup-zeego'
<Notice theme="yellow"> **Expo Router users:** This import must run _before_ `expo-router/entry`. Create an `index.js` at your project root that imports the setup first, then expo-router, and update your `package.json` main field to `"index.js"`. See the [upgrade guide](/docs/guides/how-to-upgrade#expo-router-entry-point) for details. </Notice>

Anatomy

Import all parts and piece them together.

jsx
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>
)

API Reference

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.', }, ]} />

Allowing Native Context Menu

You can call event.preventDefault() in onOpenChange to prevent the Tamagui menu from opening and allow the native browser context menu to appear instead:

tsx
<ContextMenu
  onOpenChange={(open, event) => {
    if (someCondition) {
      // prevent Tamagui menu, let native context menu show
      event?.preventDefault()
    }
  }}
>
</ContextMenu>

ContextMenu.Portal

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.', }, ]} />

ContextMenu.Trigger

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.", }, ]} />

ContextMenu.Content

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.', }, ]} />

ContextMenu.Item

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.', }, ]} />

ContextMenu.ItemTitle

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>

ContextMenu.ItemIcon

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.', }, ]} />

jsx
<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>

ContextMenu.ItemImage

A component to render an item image. For native menus, it only works on iOS. It takes the same properties as @tamagui/image.

ContextMenu.ItemSubtitle

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.', }, ]} />

ContextMenu.Group

A component that groups multiple menu items together.

<PropsTable data={[ { name: 'children', type: 'React.ReactNode', required: true, description: 'Menu items to group together.', }, ]} />

ContextMenu.CheckboxItem

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.', }, ]} />

ContextMenu.ItemIndicator

Use inside CheckboxItem or RadioItem to indicate when an item is checked. This allows you to conditionally render a checkmark.

jsx
<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.', }, ]} />

ContextMenu.Label

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.', }, ]} />

ContextMenu.Arrow

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.', }, ]} />

ContextMenu.Separator

Renders a visual divider between menu items. Web only.

ContextMenu.Sub

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.', }, ]} />

ContextMenu.SubContent

Renders the content of a sub-menu. Same props as Content, excluding side and align.

ContextMenu.SubTrigger

A menu item that opens a sub-menu on hover/focus. Accepts the same props as Item.

ContextMenu.Preview

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.", }, ]} />

jsx
<ContextMenu.Preview
  // optional props:
  preferredCommitStyle="pop" // or "dismiss"
  backgroundColor={{
    // or a color string directly
    dark: 'black',
    light: 'white',
  }}
>
  {() => <Preview />}
</ContextMenu.Preview>