Back to Tamagui

Menu

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

1.144.416.9 KB
Original Source
<HeroContainer> <MenuDemo /> </HeroContainer>
tsx

<Highlights features={[ 'Full keyboard navigation.', 'Submenus, items, icons, images, checkboxes, groups, and more.', 'Modal and non-modal modes.', 'Native menus on native platforms.', ]} />

Menu displays a list of actions or options in a floating panel triggered by a button. It supports nested submenus, keyboard navigation, native platform menus, and automatically stacks above other content.

Installation

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

bash
yarn add @tamagui/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 { Menu } from 'tamagui' // or '@tamagui/menu'

export default () => (
  <Menu>
    <Menu.Trigger asChild>
      <Button />
    </Menu.Trigger>

    <Menu.Portal zIndex={100}>
      <Menu.Content>
        <Menu.Item>
          <Menu.ItemTitle>About Notes</Menu.ItemTitle>
        </Menu.Item>
        <Menu.Item>
          <Menu.ItemTitle>Settings</Menu.ItemTitle>
        </Menu.Item>
        <Menu.Item textValue="Calendar">
          <Menu.ItemTitle>
            <Text>Calendar</Text>
          </Menu.ItemTitle>
          <Menu.ItemIcon>
            <Calendar color="gray" size="$1" />
          </Menu.ItemIcon>
        </Menu.Item>
        <Menu.Separator />
        <Menu.Sub>
          <Menu.SubTrigger>
            <Menu.ItemTitle>Actions</Menu.ItemTitle>
          </Menu.SubTrigger>
          <Menu.Portal zIndex={200}>
            <Menu.SubContent>
              <Menu.Label fontSize={'$1'}>Note settings</Menu.Label>
              <Menu.Item onSelect={onSelect} key="create-note">
                <Menu.ItemTitle>Create note</Menu.ItemTitle>
              </Menu.Item>
              <Menu.Item onSelect={onSelect} key="delete-all">
                <Menu.ItemTitle>Delete all notes</Menu.ItemTitle>
              </Menu.Item>
              <Menu.Item onSelect={onSelect} key="sync-all">
                <Menu.ItemTitle>Sync notes</Menu.ItemTitle>
              </Menu.Item>
            </Menu.SubContent>
          </Menu.Portal>
        </Menu.Sub>
      </Menu.Content>
    </Menu.Portal>
  </Menu>
)

API Reference

Contains every component for the Menu.

<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) => void', required: false, description: 'Called when the menu opens or closes.', }, { 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', default: '10', required: false, description: 'Distance between the menu and its trigger.', }, { name: 'resize', type: 'boolean', default: 'true', required: false, description: 'Constrains the menu to fit within available viewport space. When enabled, use Menu.ScrollView inside Content for scrollable overflow.', }, { name: 'unstyled', required: false, type: 'boolean', description: 'Removes all default Tamagui styles.', }, ]} />

Required for rendering the menu content.

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

Contains the content of the menu.

<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 and 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
<Menu.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 />
</Menu.ItemIcon>

A component to render an item image. For native menus, it only works on iOS. It takes the same props 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 and 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 Menu.CheckboxItem or Menu.RadioItem to indicate when an item is checked. This allows you to conditionally render a checkmark.

jsx
<Menu.ItemIndicator>
  <CheckmarkIcon />
</Menu.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 submenu 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 submenu.', }, { name: 'onOpenChange', type: '(open: boolean) => void', required: false, description: 'Called when the submenu opens or closes.', }, ]} />

Renders the content of a submenu. Same props as Menu.Content, excluding side and align.

A menu item that opens a submenu on hover or focus. Accepts the same props as Menu.Item.

A scrollable container for menu items. Use this inside Menu.Content when you have many items that may overflow. The menu automatically constrains to available viewport space (via resize prop), and ScrollView handles the overflow. Scrollbars are hidden by default.

jsx
<Menu.Content>
  <Menu.ScrollView>
  </Menu.ScrollView>
</Menu.Content>

Styling

Item Highlight Behavior

Menu items use focusStyle for highlighting rather than hoverStyle. This ensures a unified highlight experience when switching between mouse and keyboard navigation - only one item is ever highlighted at a time.

When you hover over an item, it receives focus, which triggers the focusStyle. When you use arrow keys to navigate, focus moves to the new item, removing the highlight from the previous one.

jsx
// Default behavior - uses focusStyle for highlight
<Menu.Item>
  <Menu.ItemTitle>Settings</Menu.ItemTitle>
</Menu.Item>

// Custom highlight styling - use focusStyle, not hoverStyle
<Menu.Item
  focusStyle={{ backgroundColor: '$blue5' }}
>
  <Menu.ItemTitle>Custom Highlight</Menu.ItemTitle>
</Menu.Item>
<Notice> Avoid using `hoverStyle` for background highlights on Menu.Item. This can cause "double highlighting" when switching between mouse and keyboard - where both the hovered item and the focused item appear highlighted simultaneously. </Notice>