tetra-ui Logotetra ui

Menu

A composable native menu built on Expo UI, with support for groups, separators, and nested submenus.

menu

Installation

npx shadcn@latest add @tetra-ui/menu

Usage

Compose a menu from a trigger and a content slot. Each item is built from MenuItem, MenuItemIcon, and MenuItemLabel parts.

import { Platform } from "react-native";
import { Button } from "@/components/ui/button";
import {
  Menu,
  MenuContent,
  MenuItem,
  MenuItemIcon,
  MenuItemLabel,
  MenuSeparator,
  MenuTrigger,
} from "@/components/ui/menu";
<Menu>
  <MenuTrigger>
    <Button variant="outline">Open</Button>
  </MenuTrigger>

  <MenuContent>
    <MenuItem onPress={() => {}}>
      <MenuItemIcon
        icon={Platform.select({
          ios: "gear",
          android: require("@expo/material-symbols/settings.xml"),
        })}
      />
      <MenuItemLabel>Settings</MenuItemLabel>
    </MenuItem>
    <MenuSeparator />
    <MenuItem onPress={() => {}} variant="destructive">
      <MenuItemIcon
        icon={Platform.select({
          ios: "trash",
          android: require("@expo/material-symbols/delete.xml"),
        })}
      />
      <MenuItemLabel>Delete</MenuItemLabel>
    </MenuItem>
  </MenuContent>
</Menu>

Icons

On iOS, pass an SF Symbol string to MenuItemIcon. On Android, pass a Material Symbol XML asset from @expo/material-symbols.

Use Platform.select for a cross-platform definition:

import { Platform } from "react-native";

<MenuItemIcon
  icon={Platform.select({
    ios: "person",
    android: require("@expo/material-symbols/person.xml"),
  })}
/>

Bare SF Symbol strings (for example icon="person") do not render on Android.

Disabled

Pass disabled to prevent interaction:

<MenuItem disabled>
  <MenuItemIcon
    icon={Platform.select({
      ios: "archivebox",
      android: require("@expo/material-symbols/archive.xml"),
    })}
  />
  <MenuItemLabel>Archive</MenuItemLabel>
</MenuItem>

Groups

Group related items with MenuGroup. A vertical group with a title renders as a section. A horizontal group lays icon-only items in a row.

<MenuContent>
  <MenuGroup title="My Account">
    <MenuItem onPress={() => {}}>
      <MenuItemIcon
        icon={Platform.select({
          ios: "person",
          android: require("@expo/material-symbols/person.xml"),
        })}
      />
      <MenuItemLabel>Profile</MenuItemLabel>
    </MenuItem>
  </MenuGroup>

  <MenuSeparator />

  <MenuGroup direction="horizontal" title="Quick Actions">
    <MenuItem onPress={() => {}}>
      <MenuItemIcon
        icon={Platform.select({
          ios: "plus",
          android: require("@expo/material-symbols/add.xml"),
        })}
      />
    </MenuItem>
    <MenuItem onPress={() => {}}>
      <MenuItemIcon
        icon={Platform.select({
          ios: "magnifyingglass",
          android: require("@expo/material-symbols/search.xml"),
        })}
      />
    </MenuItem>
  </MenuGroup>
</MenuContent>

On Android, horizontal groups use a compact row layout. Icon-only items work best in horizontal groups.

Nest a submenu with MenuSub, MenuSubTrigger, and MenuSubContent:

<MenuContent>
  <MenuSub>
    <MenuSubTrigger>
      <MenuItem>
        <MenuItemIcon
          icon={Platform.select({
            ios: "2.circle",
            android: require("@expo/material-symbols/counter_2.xml"),
          })}
        />
        <MenuItemLabel>More</MenuItemLabel>
      </MenuItem>
    </MenuSubTrigger>
    <MenuSubContent>
      <MenuItem onPress={() => {}}>
        <MenuItemIcon
          icon={Platform.select({
            ios: "doc",
            android: require("@expo/material-symbols/description.xml"),
          })}
        />
        <MenuItemLabel>Sub Item</MenuItemLabel>
      </MenuItem>
    </MenuSubContent>
  </MenuSub>
</MenuContent>

Platform behavior

PlatformNative primitive
iOSSwiftUI Menu
AndroidJetpack Compose DropdownMenu
WebNo-op stubs

Menus use native Expo UI primitives under the hood. Selecting an item dismisses the menu automatically on iOS and Android.

Changelog

08-06-2026 - Add support for disabled items & remove MenuItemIcon.select

  • Add support for disabled items.
  • Remove MenuItemIcon.select as it is no longer needed. Use Platform.select instead.

On this page