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>Selected
Pass selected to render a checkmark on selectable items. Omit selected for regular action items. You control the state and update it in onPress.
Single-select (for example, sort options):
import { useState } from "react";
const [sort, setSort] = useState<"name" | "date">("name");
<MenuItem selected={sort === "name"} onPress={() => setSort("name")}>
<MenuItemIcon
icon={Platform.select({
ios: "textformat",
android: require("@expo/material-symbols/sort_by_alpha.xml"),
})}
/>
<MenuItemLabel>Name</MenuItemLabel>
</MenuItem>
<MenuItem selected={sort === "date"} onPress={() => setSort("date")}>
<MenuItemIcon
icon={Platform.select({
ios: "calendar",
android: require("@expo/material-symbols/calendar_today.xml"),
})}
/>
<MenuItemLabel>Date</MenuItemLabel>
</MenuItem>Multi-toggle (for example, filters):
const [showCompleted, setShowCompleted] = useState(true);
<MenuItem
selected={showCompleted}
onPress={() => setShowCompleted((value) => !value)}
>
<MenuItemIcon
icon={Platform.select({
ios: "checkmark.circle",
android: require("@expo/material-symbols/check_circle.xml"),
})}
/>
<MenuItemLabel>Show completed</MenuItemLabel>
</MenuItem>On iOS, selectable items use SwiftUI Toggle and render a trailing checkmark when selected is true. On Android, a trailing ✓ is shown. Destructive items with selected do not show a checkmark on iOS because Toggle has no destructive role.
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 a regular item dismisses the menu automatically on iOS and Android. Selectable items on iOS may keep the menu open after tap (native SwiftUI Toggle behavior); on Android, selectable items still dismiss the menu.
API Reference
Native surface on Expo UI (Menu / DropdownMenu). Documented props are the tetra-ui API; underlying Expo primitives are managed internally.
Menu
Root menu. Requires MenuTrigger and MenuContent children.
MenuTrigger
Single React element rendered as the menu label / anchor (typically a pressable).
Prop
Type
MenuContent
Menu items, groups, separators, and submenus.
MenuGroup
Groups items. Vertical uses a section; horizontal uses a control group / row.
Prop
Type
MenuItem
A tappable row. When selected is defined, iOS uses a native toggle; Android shows a trailing checkmark.
Prop
Type
MenuItemLabel
Label text for the item.
Prop
Type
MenuItemIcon
Leading icon via Expo UI Icon name (SF Symbol on iOS; drawable / material symbol on Android — prefer platform-selected names for cross-platform).
Prop
Type
MenuSeparator
Native divider between items. No custom props.
MenuSub
Nested submenu. Same child contract as Menu (MenuSubTrigger + MenuSubContent).
MenuSubTrigger
Same shape as MenuTrigger for the submenu entry.
MenuSubContent
Same shape as MenuContent for nested items.
Changelog
2026-07-02 - Add support for selected items
- Add optional
selectedprop toMenuItemfor checkmark states. - iOS uses SwiftUI
Toggle; Android renders a trailing checkmark.
2026-06-08 - Add support for disabled items & remove MenuItemIcon.select
- Add support for disabled items.
- Remove
MenuItemIcon.selectas it is no longer needed. UsePlatform.selectinstead.