Reference version

Menu

A SwiftUI Menu component for displaying dropdown menus.

iOS
tvOS
Bundled version:
~55.0.11

For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

Expo UI Menu matches the official SwiftUI Menu API and supports styling via the buttonStyle modifier. Menu opens on a single tap. For long-press interactions, use ContextMenu instead.

Note: On tvOS, Menu requires tvOS 17.0 or later.

Installation

Terminal
npx expo install @expo/ui

If you are installing this in an existing React Native app, make sure to install expo in your project.

Usage

Simple text label

SimpleMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; export default function SimpleMenuExample() { return ( <Host matchContents> <Menu label="Options"> <Button label="Option 1" onPress={() => console.log('Option 1')} /> <Button label="Option 2" onPress={() => console.log('Option 2')} /> <Button label="Option 3" onPress={() => console.log('Option 3')} /> </Menu> </Host> ); }

Text label with SF Symbol

MenuWithIconExample.tsx
import { Host, Menu, Button, Divider } from '@expo/ui/swift-ui'; export default function MenuWithIconExample() { return ( <Host matchContents> <Menu label="More" systemImage="ellipsis.circle"> <Button label="Settings" systemImage="gear" onPress={() => console.log('Settings')} /> <Button label="Profile" systemImage="person" onPress={() => console.log('Profile')} /> <Divider /> <Button label="Delete" role="destructive" systemImage="trash" onPress={() => console.log('Delete')} /> </Menu> </Host> ); }

Custom label

You can pass a React node as the label for custom styling.

CustomLabelMenuExample.tsx
import { Host, Menu, Button, Text } from '@expo/ui/swift-ui'; export default function CustomLabelMenuExample() { return ( <Host matchContents> <Menu label={<Text color="accentColor">Custom Label</Text>}> <Button label="Action 1" onPress={() => console.log('Action 1')} /> <Button label="Action 2" onPress={() => console.log('Action 2')} /> </Menu> </Host> ); }

Nested menu

Menus can be nested to create submenus.

NestedMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; export default function NestedMenuExample() { return ( <Host matchContents> <Menu label="Main Menu"> <Button label="Item 1" onPress={() => console.log('Item 1')} /> <Menu label="Submenu"> <Button label="Sub Item 1" onPress={() => console.log('Sub Item 1')} /> <Button label="Sub Item 2" onPress={() => console.log('Sub Item 2')} /> </Menu> <Button label="Item 2" onPress={() => console.log('Item 2')} /> </Menu> </Host> ); }

With primary action

When onPrimaryAction is provided, a single tap triggers the primary action while a long-press shows the menu.

PrimaryActionMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; export default function PrimaryActionMenuExample() { return ( <Host matchContents> <Menu label="Tap or Hold" systemImage="play.circle" onPrimaryAction={() => console.log('Primary action triggered!')}> <Button label="Menu Item 1" onPress={() => console.log('Menu Item 1')} /> <Button label="Menu Item 2" onPress={() => console.log('Menu Item 2')} /> <Button label="Menu Item 3" onPress={() => console.log('Menu Item 3')} /> </Menu> </Host> ); }

Styling with modifiers

You can use the buttonStyle modifier to change the appearance of the menu trigger.

StyledMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; import { buttonStyle } from '@expo/ui/swift-ui/modifiers'; export default function StyledMenuExample() { return ( <Host matchContents> <Menu label="Styled Menu" modifiers={[buttonStyle('borderedProminent')]}> <Button label="Styled Action 1" onPress={() => console.log('Styled 1')} /> <Button label="Styled Action 2" onPress={() => console.log('Styled 2')} /> </Menu> </Host> ); }

Glass menu

To create a menu with the iOS Liquid Glass appearance, use buttonStyle('glass') or buttonStyle('glassProminent') on the Menu component.

Important: Do not apply the glassEffect() modifier to the Menu's label view to achieve a glass look. This causes a visual artifact where a rectangular halo briefly appears behind the trigger when the menu is dismissed. Always use buttonStyle instead, which correctly integrates with the Menu's dismiss animation.

GlassMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; import { buttonStyle } from '@expo/ui/swift-ui/modifiers'; export default function GlassMenuExample() { return ( <Host matchContents> <Menu label="Glass Menu" systemImage="ellipsis.circle" modifiers={[buttonStyle('glass')]}> <Button label="Action 1" onPress={() => console.log('Action 1')} /> <Button label="Action 2" onPress={() => console.log('Action 2')} /> </Menu> </Host> ); }

For a more prominent glass effect, use glassProminent:

GlassProminentMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; import { buttonStyle } from '@expo/ui/swift-ui/modifiers'; export default function GlassProminentMenuExample() { return ( <Host matchContents> <Menu label="Glass Prominent Menu" systemImage="slider.horizontal.3" modifiers={[buttonStyle('glassProminent')]}> <Button label="Settings" systemImage="gear" onPress={() => console.log('Settings')} /> <Button label="Filter" systemImage="line.3.horizontal.decrease" onPress={() => console.log('Filter')} /> </Menu> </Host> ); }

With control group

Use a ControlGroup inside a menu to render a horizontal row of icon buttons, similar to the quick actions row in Apple Music or Safari menus.

MenuWithControlGroupExample.tsx
import { Host, Menu, ControlGroup, Button, Section, Divider } from '@expo/ui/swift-ui'; export default function MenuWithControlGroupExample() { return ( <Host matchContents> <Menu label="Song Options" systemImage="ellipsis.circle"> <ControlGroup> <Button systemImage="plus" label="Add" onPress={() => console.log('Add')} /> <Button systemImage="star" label="Favorite" onPress={() => console.log('Favorite')} /> <Button systemImage="square.and.arrow.up" label="Share" onPress={() => console.log('Share')} /> </ControlGroup> <Section> <Button systemImage="text.badge.plus" label="Add to a Playlist" onPress={() => console.log('Add to Playlist')} /> <Button systemImage="antenna.radiowaves.left.and.right" label="Create Station" onPress={() => console.log('Create Station')} /> </Section> <Divider /> <Button systemImage="hand.thumbsdown" label="Suggest Less" onPress={() => console.log('Suggest Less')} /> </Menu> </Host> ); }

Icon only menu button

Use the labelStyle('iconOnly') modifier to display only the icon without the label text. The label prop should still be provided for accessibility purposes.

IconOnlyMenuExample.tsx
import { Host, Menu, Button } from '@expo/ui/swift-ui'; import { labelStyle } from '@expo/ui/swift-ui/modifiers'; export default function IconOnlyMenuExample() { return ( <Host matchContents> <Menu label="Icon Only Button" systemImage="gear" modifiers={[labelStyle('iconOnly')]}> <Button label="Menu Item 1" onPress={() => console.log('Menu Item 1')} /> <Button label="Menu Item 2" onPress={() => console.log('Menu Item 2')} /> <Button label="Menu Item 3" onPress={() => console.log('Menu Item 3')} /> </Menu> </Host> ); }

API

import { Menu } from '@expo/ui/swift-ui';

Component

iOS
tvOS

Type: React.Element<MenuProps>

Displays a dropdown menu when tapped.

Props for the Menu component.

MenuProps

children

iOS
tvOS
Type: ReactNode

The menu's content items, which are shown when the menu is opened. Can contain Button, Toggle, Picker, Section, Divider or nested Menu components.

label

iOS
tvOS
Literal type: union

The label for the menu trigger. Can be a string for simple text labels, or a ReactNode for custom label content.

Acceptable values are: string | ReactNode

onPrimaryAction

iOS
tvOS
Optional • Type: () => void

A callback that is invoked when the user taps the menu label. When provided, a single tap triggers this action, while a long-press shows the menu. When not provided, a single tap shows the menu.

systemImage

iOS
tvOS
Optional • Type: string

An SF Symbol name to display alongside the label. Only used when label is a string.