---
modificationDate: April 02, 2026
title: Stack Toolbar
description: Learn how to use the iOS toolbar in Stack navigation with Expo Router.
---

<AgentInstructions>

## Submitting Feedback

If this page contains errors, outdated information, or gaps that blocked you from completing a task, report it so the docs can be improved:

curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/router/advanced/stack-toolbar/","feedback":"🤖 Agent feedback: <specific, actionable description>"}'

Only submit when you have something specific and actionable to report.

</AgentInstructions>

# Stack Toolbar

Learn how to use the iOS toolbar in Stack navigation with Expo Router.

> For the complete documentation index, see [llms.txt](/llms.txt). Use this file to discover all available pages.

> `Stack.Toolbar` is an [alpha](/more/release-statuses#alpha) API available on **iOS only** in **Expo SDK 55** and later. The API is subject to breaking changes.

[`Stack.Toolbar`](/versions/latest/sdk/router/stack#stacktoolbar) 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`](/versions/latest/sdk/router/stack#stacktoolbarbutton) 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.

```tsx
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](https://developer.apple.com/sf-symbols/), Apple's built-in icon library. Pass the symbol name directly to the `icon` prop:

```tsx
<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.

> Using custom images inside submenus (`Stack.Toolbar.Menu`) in header placements requires `react-native-screens` 4.24.0 or later. SDK 55 bundles `~4.23.0`, so you need to install `react-native-screens@~4.24.0` manually to use this feature.

```tsx
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:

```tsx
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 */}
    </>
  );
}
```

> The `useImage` and `image` prop 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`](/versions/latest/sdk/router/stack#stacktoolbarmenu) to group them into a dropdown menu:

```tsx
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`](/versions/latest/sdk/router/stack#stacktoolbarmenuaction) 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:

```tsx
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"`):

```tsx
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`](/versions/latest/sdk/router/stack#stacktoolbarspacer) 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`](/versions/latest/sdk/router/stack#stacktoolbaricon), [`Stack.Toolbar.Label`](/versions/latest/sdk/router/stack#stacktoolbarlabel), and [`Stack.Toolbar.Badge`](/versions/latest/sdk/router/stack#stacktoolbarbadge) to compose the button content:

```tsx
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 (`left` or `right`), not in the bottom toolbar.

## Embedding custom views

When you need something beyond buttons and menus, use [`Stack.Toolbar.View`](/versions/latest/sdk/router/stack#stacktoolbarview) to embed any React Native component:

```tsx
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:

```tsx
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 */}
    </>
  );
}
```

## Common problems

Liquid glass toolbar buttons flicker in dark mode on iOS 26

Toolbar buttons with liquid glass styling may flicker or flash their background when navigating between screens in dark mode on iOS 26. This happens because React Navigation's default theme doesn't match the system dark mode, causing visual artifacts in the liquid glass rendering.

To fix this, wrap your root layout with `<ThemeProvider>` from `@react-navigation/native` using the appropriate theme:

```tsx
import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native';
import { Stack } from 'expo-router';
import { useColorScheme } from 'react-native';

export default function RootLayout() {
  const colorScheme = useColorScheme();

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack />
    </ThemeProvider>
  );
}
```
White background flashes when navigating between screens

A white flash between screen transitions usually means the navigation stack is using a light background while your app uses a dark theme. This is especially noticeable when screens contain toolbar items, as the flash contrasts with the toolbar styling.

To fix this, wrap your root layout with React Navigation's `<ThemeProvider>` and pass the appropriate theme:

```tsx
import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native';
import { Stack } from 'expo-router';
import { useColorScheme } from 'react-native';

export default function RootLayout() {
  const colorScheme = useColorScheme();

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack />
    </ThemeProvider>
  );
}
```
Large title does not collapse when scrolling

When using `headerLargeTitle: true` (or `<Stack.Screen.Title large>`) alongside `Stack.Toolbar`, the large title may not collapse on scroll. This happens when the scrollable view is not the direct first child of the screen component.

To fix this, ensure `ScrollView` or `FlatList` is the first child rendered by your screen component. If you need a wrapper, set `collapsable={false}` on it:

```tsx
import { Stack } from 'expo-router';
import { ScrollView, View, Text } from 'react-native';

export default function Home() {
  return (
    <ScrollView>
      <Stack.Screen.Title large>Home</Stack.Screen.Title>
      <Text>Content here</Text>
    </ScrollView>
  );
}
```

If you need to wrap the `ScrollView`, set `collapsable={false}` on the wrapper:

```tsx
import { Stack } from 'expo-router';
import { ScrollView, View, Text } from 'react-native';

export default function Home() {
  return (
    <View collapsable={false}>
      <ScrollView>
        <Stack.Screen.Title large>Home</Stack.Screen.Title>
        <Text>Content here</Text>
      </ScrollView>
    </View>
  );
}
```

## Known limitations

iOS only

`Stack.Toolbar` is only available on iOS. On Android and web, the component will not render.

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.

Cannot nest toolbars

You cannot nest `Stack.Toolbar` components inside each other.

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](/versions/latest/sdk/router/stack#stacktoolbar).
