Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Official Expo team skill for building native UI in Expo apps, fine-tuned for Opus models and usable with Claude Code or Cursor.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/toolbar-and-headers.md
1# Toolbars and headers23Add native iOS toolbar items to Stack screens. Items can be placed in the header (left/right) or in a bottom toolbar area.45**Important:** iOS only. Available in Expo SDK 55+.67## Notes app example89```tsx10import { Stack } from "expo-router";11import { ScrollView } from "react-native";1213export default function FoldersScreen() {14return (15<>16{/* ScrollView must be the first child of the screen */}17<ScrollView18style={{ flex: 1 }}19contentInsetAdjustmentBehavior="automatic"20>21{/* Screen content */}22</ScrollView>23<Stack.Screen.Title large>Folders</Stack.Screen.Title>24<Stack.SearchBar placeholder="Search" onChangeText={() => {}} />25{/* Header toolbar - right side */}26<Stack.Toolbar placement="right">27<Stack.Toolbar.Button icon="folder.badge.plus" onPress={() => {}} />28<Stack.Toolbar.Button onPress={() => {}}>Edit</Stack.Toolbar.Button>29</Stack.Toolbar>3031{/* Bottom toolbar */}32<Stack.Toolbar placement="bottom">33<Stack.Toolbar.SearchBarSlot />34<Stack.Toolbar.Button35icon="square.and.pencil"36onPress={() => {}}37separateBackground38/>39</Stack.Toolbar>40</>41);42}43```4445## Mail inbox example4647```tsx48import { Color, Stack } from "expo-router";49import { useState } from "react";50import { ScrollView, Text, View } from "react-native";5152export default function InboxScreen() {53const [isFilterOpen, setIsFilterOpen] = useState(false);54return (55<>56<ScrollView57style={{ flex: 1 }}58contentInsetAdjustmentBehavior="automatic"59contentContainerStyle={{ paddingHorizontal: 16 }}60>61{/* Screen content */}62</ScrollView>63<Stack.Screen options={{ headerTransparent: true }} />64<Stack.Screen.Title>Inbox</Stack.Screen.Title>65<Stack.SearchBar placeholder="Search" onChangeText={() => {}} />66{/* Header toolbar - right side */}67<Stack.Toolbar placement="right">68<Stack.Toolbar.Button onPress={() => {}}>Select</Stack.Toolbar.Button>69<Stack.Toolbar.Menu icon="ellipsis">70<Stack.Toolbar.Menu inline>71<Stack.Toolbar.Menu inline title="Sort By">72<Stack.Toolbar.MenuAction isOn>73Categories74</Stack.Toolbar.MenuAction>75<Stack.Toolbar.MenuAction>List</Stack.Toolbar.MenuAction>76</Stack.Toolbar.Menu>77<Stack.Toolbar.MenuAction icon="info.circle">78About categories79</Stack.Toolbar.MenuAction>80</Stack.Toolbar.Menu>81<Stack.Toolbar.MenuAction icon="person.circle">82Show Contact Photos83</Stack.Toolbar.MenuAction>84</Stack.Toolbar.Menu>85</Stack.Toolbar>8687{/* Bottom toolbar */}88<Stack.Toolbar placement="bottom">89<Stack.Toolbar.Button90icon="line.3.horizontal.decrease"91selected={isFilterOpen}92onPress={() => setIsFilterOpen((prev) => !prev)}93/>94<Stack.Toolbar.View hidden={!isFilterOpen}>95<View style={{ width: 70, height: 32, justifyContent: "center" }}>96<Text style={{ fontSize: 12, fontWeight: 700 }}>Filter by</Text>97<Text98style={{99fontSize: 12,100fontWeight: 700,101color: Color.ios.systemBlue,102}}103>104Unread105</Text>106</View>107</Stack.Toolbar.View>108<Stack.Toolbar.Spacer />109<Stack.Toolbar.SearchBarSlot />110<Stack.Toolbar.Button111icon="square.and.pencil"112onPress={() => {}}113separateBackground114/>115</Stack.Toolbar>116</>117);118}119```120121## Placement122123- `"left"` - Header left124- `"right"` - Header right125- `"bottom"` (default) - Bottom toolbar126127## Components128129### Button130131- Icon button: `<Stack.Toolbar.Button icon="star.fill" onPress={() => {}} />`132- Text button: `<Stack.Toolbar.Button onPress={() => {}}>Done</Stack.Toolbar.Button>`133134**Props:** `icon`, `image`, `onPress`, `disabled`, `hidden`, `variant` (`"plain"` | `"done"` | `"prominent"`), `tintColor`135136### Menu137138Dropdown menu for grouping actions.139140```tsx141<Stack.Toolbar.Menu icon="ellipsis">142<Stack.Toolbar.Menu inline>143<Stack.Toolbar.MenuAction>Sort by Recently Added</Stack.Toolbar.MenuAction>144<Stack.Toolbar.MenuAction isOn>145Sort by Date Captured146</Stack.Toolbar.MenuAction>147</Stack.Toolbar.Menu>148<Stack.Toolbar.Menu title="Filter">149<Stack.Toolbar.Menu inline>150<Stack.Toolbar.MenuAction isOn icon="square.grid.2x2">151All Items152</Stack.Toolbar.MenuAction>153</Stack.Toolbar.Menu>154<Stack.Toolbar.MenuAction icon="heart">Favorites</Stack.Toolbar.MenuAction>155<Stack.Toolbar.MenuAction icon="photo">Photos</Stack.Toolbar.MenuAction>156<Stack.Toolbar.MenuAction icon="video">Videos</Stack.Toolbar.MenuAction>157</Stack.Toolbar.Menu>158</Stack.Toolbar.Menu>159```160161**Menu Props:** All Button props plus `title`, `inline`, `palette`, `elementSize` (`"small"` | `"medium"` | `"large"`)162163**MenuAction Props:** `icon`, `onPress`, `isOn`, `destructive`, `disabled`, `subtitle`164165When creating a palette with dividers, use `inline` combined with `elementSize="small"`. `palette` will not apply dividers on iOS 26.166167### Spacer168169```tsx170<Stack.Toolbar.Spacer /> // Bottom toolbar - flexible171<Stack.Toolbar.Spacer width={16} /> // Header - requires explicit width172```173174### View175176Embed custom React Native components. When adding a custom view make sure that there is only a single child with **explicit width and height**.177178```tsx179<Stack.Toolbar.View>180<View style={{ width: 70, height: 32, justifyContent: "center" }}>181<Text style={{ fontSize: 12, fontWeight: 700 }}>Filter by</Text>182</View>183</Stack.Toolbar.View>184```185186You can pass custom components to views as well:187188```tsx189function CustomFilterView() {190return (191<View style={{ width: 70, height: 32, justifyContent: "center" }}>192<Text style={{ fontSize: 12, fontWeight: 700 }}>Filter by</Text>193</View>194);195}196...197<Stack.Toolbar.View>198<CustomFilterView />199</Stack.Toolbar.View>200```201202## Recommendations203204- When creating more complex headers, extract them to a single component205206```tsx207export default function Page() {208return (209<>210<ScrollView>{/* Screen content */}</ScrollView>211<InboxHeader />212</>213);214}215216function InboxHeader() {217return (218<>219<Stack.Screen.Title>Inbox</Stack.Screen.Title>220<Stack.SearchBar placeholder="Search" onChangeText={() => {}} />221<Stack.Toolbar placement="right">{/* Toolbar buttons */}</Stack.Toolbar>222</>223);224}225```226227- When using `Stack.Toolbar`, make sure that all `Stack.Toolbar.*` components are wrapped inside `Stack.Toolbar` component.228229This will **not work**:230231```tsx232function Buttons() {233return (234<>235<Stack.Toolbar.Button icon="star.fill" onPress={() => {}} />236<Stack.Toolbar.Button onPress={() => {}}>Done</Stack.Toolbar.Button>237</>238);239}240241function Page() {242return (243<>244<ScrollView>{/* Screen content */}</ScrollView>245<Stack.Toolbar placement="right">246<Buttons /> {/* ❌ This will NOT work */}247</Stack.Toolbar>248</>249);250}251```252253This will work:254255```tsx256function ToolbarWithButtons() {257return (258<Stack.Toolbar>259<Stack.Toolbar.Button icon="star.fill" onPress={() => {}} />260<Stack.Toolbar.Button onPress={() => {}}>Done</Stack.Toolbar.Button>261</Stack.Toolbar>262);263}264265function Page() {266return (267<>268<ScrollView>{/* Screen content */}</ScrollView>269<ToolbarWithButtons /> {/* ✅ This will work */}270</>271);272}273```274275## Limitations276277- iOS only278- `placement="bottom"` can only be used inside screen components (not in layout files)279- `Stack.Toolbar.Badge` only works with `placement="left"` or `"right"`280- Header Spacers require explicit `width`281282## Reference283284Docs https://docs.expo.dev/versions/unversioned/sdk/router - read to see the full API.285