Stack Toolbar
Edit page
Learn how to use the iOS toolbar in Stack navigation with Expo Router.
Stack.Toolbaris an alpha API available on iOS only in Expo SDK 55 and later. The API is subject to breaking changes.
Stack.Toolbar lets you add native iOS toolbar items to your Stack screens. You can place buttons, menus, and custom views in the header (left or right side) or in the bottom toolbar area.
Adding header buttons
Use Stack.Toolbar.Button within Stack.Toolbar with placement="right" or placement="left" to add buttons to the navigation header. This is useful for actions like favoriting, sharing, or editing content.
import { useState } from 'react'; import { Stack } from 'expo-router'; import { View, Text, Alert } from 'react-native'; export default function NoteScreen() { const [isFavorite, setIsFavorite] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button icon={isFavorite ? 'star.fill' : 'star'} onPress={() => setIsFavorite(!isFavorite)} /> <Stack.Toolbar.Button icon="square.and.arrow.up" onPress={() => Alert.alert('Share')} /> </Stack.Toolbar> <Stack.Toolbar placement="left"> <Stack.Toolbar.Button icon="sidebar.left" onPress={() => Alert.alert('Sidebar')} /> </Stack.Toolbar> <View style={{ flex: 1, padding: 16 }}> <Text>Note content...</Text> </View> </> ); }
Icons
Toolbar buttons support two types of icons: SF Symbols and custom images.
SF Symbols
The easiest way to add icons is using SF Symbols, Apple's built-in icon library. Pass the symbol name directly to the icon prop:
<Stack.Toolbar.Button icon="star.fill" onPress={() => {}} /> <Stack.Toolbar.Button icon="square.and.arrow.up" onPress={() => {}} /> <Stack.Toolbar.Menu icon="ellipsis.circle">{/* ... */}</Stack.Toolbar.Menu>
You can browse available symbols in Apple's SF Symbols app.
Custom images
You can also use custom images. In header toolbars (placement="left" or placement="right"), pass an image source directly to the icon prop:
import { Stack } from 'expo-router'; export default function Page() { return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button icon={require('./assets/expo.png')} onPress={() => {}} /> </Stack.Toolbar> {/* Screen content */} </> ); }
In the bottom toolbar, use the useImage hook from expo-image and pass the result to the image prop:
import { Stack } from 'expo-router'; import { useImage } from 'expo-image'; export default function Page() { const customIcon = useImage('https://simpleicons.org/icons/expo.svg', { maxWidth: 24, maxHeight: 24, }); return ( <> <Stack.Toolbar> <Stack.Toolbar.Button image={customIcon} onPress={() => {}} /> </Stack.Toolbar> {/* Screen content */} </> ); }
TheuseImageandimageprop pattern for bottom toolbar custom images is a temporary API and may change in future releases.
Building action menus
For screens with multiple actions, use Stack.Toolbar.Menu to group them into a dropdown menu:
import { useState } from 'react'; import { Stack } from 'expo-router'; import { Alert } from 'react-native'; export default function EmailScreen() { const [isArchived, setIsArchived] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Menu icon="ellipsis.circle"> <Stack.Toolbar.MenuAction icon="arrowshape.turn.up.left" onPress={() => Alert.alert('Reply')}> Reply </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon="arrowshape.turn.up.right" onPress={() => Alert.alert('Forward')}> Forward </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon={isArchived ? 'tray.full' : 'archivebox'} isOn={isArchived} onPress={() => setIsArchived(!isArchived)}> {isArchived ? 'Unarchive' : 'Archive'} </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon="trash" destructive onPress={() => Alert.alert('Delete')}> Delete </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* Email content */} </> ); }
The isOn prop on Stack.Toolbar.MenuAction shows a checkmark next to the action, useful for toggle states. The destructive prop styles the action in red to indicate a dangerous operation.
Nested submenus
For more complex menus, nest Stack.Toolbar.Menu inside another menu. Use the inline prop to display submenu items directly without collapsing:
import { useState } from 'react'; import { Stack } from 'expo-router'; export default function EmailScreen() { const [sortBy, setSortBy] = useState<'name' | 'date' | 'size'>('name'); const [showHiddenFiles, setShowHiddenFiles] = useState(false); return ( <> <Stack.Toolbar> <Stack.Toolbar.Menu icon="ellipsis.circle"> {/* Inline submenu - options appear directly in the menu */} <Stack.Toolbar.Menu inline title="Sort By"> <Stack.Toolbar.MenuAction isOn={sortBy === 'name'} onPress={() => setSortBy('name')}> Name </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction isOn={sortBy === 'date'} onPress={() => setSortBy('date')}> Date </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction isOn={sortBy === 'size'} onPress={() => setSortBy('size')}> Size </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> {/* Nested submenu - opens as a separate menu */} <Stack.Toolbar.Menu title="Preferences"> <Stack.Toolbar.MenuAction isOn={showHiddenFiles} onPress={() => setShowHiddenFiles(!showHiddenFiles)}> Show Hidden Files </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* Email content */} </> ); }
Using the bottom toolbar
iOS apps commonly have a bottom toolbar for primary actions. To add one, use Stack.Toolbar without a placement prop (it defaults to "bottom"):
import { Stack } from 'expo-router'; import { Alert } from 'react-native'; export default function PhotosScreen() { return ( <> <Stack.Toolbar> <Stack.Toolbar.Button icon="photo.on.rectangle" onPress={() => Alert.alert('Select')}> Select </Stack.Toolbar.Button> <Stack.Toolbar.Spacer /> <Stack.Toolbar.Button icon="plus" onPress={() => Alert.alert('Add')}> Add </Stack.Toolbar.Button> </Stack.Toolbar> </> ); }
Stack.Toolbar.Spacer creates flexible space between items, pushing them to opposite sides. This is how you achieve layouts like having buttons on both ends of the toolbar.
Bottom toolbars can only be used inside page components, not in layout files.
Adding badges to buttons
In header toolbars, you can add badges to indicate counts or status. Use Stack.Toolbar.Icon, Stack.Toolbar.Label, and Stack.Toolbar.Badge to compose the button content:
import { Stack } from 'expo-router'; export default function InboxScreen() { const unreadCount = 5; return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button onPress={() => {}}> <Stack.Toolbar.Icon sf="bell" /> <Stack.Toolbar.Label>Notifications</Stack.Toolbar.Label> {unreadCount > 0 && <Stack.Toolbar.Badge>{String(unreadCount)}</Stack.Toolbar.Badge>} </Stack.Toolbar.Button> </Stack.Toolbar> {/* Screen content */} </> ); }
Badges only work in header placements (leftorright), not in the bottom toolbar.
Embedding custom views
When you need something beyond buttons and menus, use Stack.Toolbar.View to embed any React Native component:
import { Stack } from 'expo-router'; import { Pressable, Alert } from 'react-native'; import { SymbolView } from 'expo-symbols'; export default function SearchScreen() { return ( <> <Stack.Toolbar> <Stack.Toolbar.View> <Pressable style={{ width: 32, height: 32, justifyContent: 'center', alignItems: 'center' }} onPress={() => { Alert.alert('Filter pressed'); }}> <SymbolView name="line.3.horizontal.decrease.circle" size={24} /> </Pressable> </Stack.Toolbar.View> </Stack.Toolbar> {/* Screen content */} </> ); }
Showing and hiding items dynamically
Use the hidden prop to toggle toolbar items based on state:
import { useState } from 'react'; import { Stack } from 'expo-router'; export default function DocumentScreen() { const [isEditing, setIsEditing] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button hidden={isEditing} icon="pencil" onPress={() => setIsEditing(true)} /> <Stack.Toolbar.Button hidden={!isEditing} onPress={() => setIsEditing(false)}> Done </Stack.Toolbar.Button> </Stack.Toolbar> {/* Document content */} </> ); }
Known limitations
Bottom toolbar only in page components
The bottom toolbar can only be used inside page components, not in layout files. This is because the bottom toolbar needs to be associated with a specific screen's content.
Badge only in header placements
Stack.Toolbar.Badge is only supported when using placement="left" or placement="right". Badges are not displayed in the bottom toolbar.
Learn more
For complete API documentation, including all available props, see the Stack.Toolbar API reference.