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

Installation
npx shadcn@latest add @tetra-ui/menuUsage
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.
Submenus
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
| Platform | Native primitive |
|---|---|
| iOS | SwiftUI Menu |
| Android | Jetpack Compose DropdownMenu |
| Web | No-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.selectas it is no longer needed. UsePlatform.selectinstead.