SwiftUI NavigationStack, NavigationSplitView, and Toolbar entries under One.iOS

One.iOS navigation chrome is SwiftUI’s own: a real NavigationStack whose
root is the React Native content beside it, with the navigation bar filled by
Toolbar entries, and a two or three column NavigationSplitView for
sidebar layouts. Title, display mode, and every other scalar navigation
modifier reach the stack through swiftStyle, so the bar has no bespoke
props.
This is the SwiftUI toolbar that fills the navigation bar, distinct from the
UIKit bottom toolbar (One.iOS.ToolbarHost and One.iOS.BarButtonItem)
covered in Toolbar and Menus.
import { Text } from 'react-native'import { One } from 'one'
function Mailbox({ save }: { save: () => void }) { return ( <One.iOS.NavigationStack style={{ flex: 1 }} swiftStyle={{ navigationTitleWithText: 'Mailbox', navigationBarTitleDisplayMode: 'inline' }} > <One.iOS.Toolbar> <One.iOS.ToolbarItem placement="primaryAction"> <One.iOS.Button label="Save" onPress={save} /> </One.iOS.ToolbarItem> </One.iOS.Toolbar> <Text>Your messages.</Text> </One.iOS.NavigationStack> )}The toolbar fills the navigation bar while the React Native text stays the
stack root. Set the title with swiftStyle, for example
navigationTitleWithText: 'Mailbox' with
navigationBarTitleDisplayMode: 'inline'.
The stack renders through a Fabric view hosting a SwiftUI NavigationStack.
At render, children split into two groups: Toolbar elements, which read as
ordered toolbar entries, and everything else, which becomes the root content
hosted in the box SwiftUI proposes. The stack merges every toolbar it is
given. A split view instead takes column markers, each hosting its own React
Native content with an optional toolbar.
Toolbar entries are markers: Toolbar, ToolbarItem, ToolbarItemGroup,
ToolbarSpacer, and the column markers never render where they are written
and throw when placed anywhere but their parent. Placements use SwiftUI names
checked against the device’s iOS version, so a placement newer than the
running iOS throws before any native prop carries it.
NavigationStack is a SwiftUI navigation stack whose root is the React Native
content beside it. Children are Toolbar elements or React Native content;
ToolbarItem, ToolbarItemGroup, and ToolbarSpacer belong inside a
Toolbar, never directly in the stack.
import { Text } from 'react-native'import { One } from 'one'
function Inbox({ save }: { save: () => void }) { return ( <One.iOS.NavigationStack style={{ flex: 1 }}> <One.iOS.Toolbar> <One.iOS.ToolbarItem placement="primaryAction"> <One.iOS.Button label="Save" onPress={save} /> </One.iOS.ToolbarItem> </One.iOS.Toolbar> <Text>Your messages.</Text> </One.iOS.NavigationStack> )}| Props | Type | Description |
|---|---|---|
children (required)ReactNodeToolbar elements and the root content. | ReactNode | Toolbar elements and the root content. |
The stack reads these swiftStyle modifiers inside the NavigationStack, so
each one changes the navigation bar of the stack it is set on.
<One.iOS.NavigationStack style={{ flex: 1 }} swiftStyle={{ navigationTitleWithText: 'Accounts', navigationBarTitleDisplayMode: 'inline', toolbarBackground: { visibility: 'visible', bars: 'navigationBar' }, toolbarColorScheme: { colorScheme: 'dark', bars: 'navigationBar' }, navigationBarBackButtonHidden: true, }}> <Text>Your accounts.</Text></One.iOS.NavigationStack>| Modifier | What it changes |
|---|---|
navigationTitleWithText, navigationBarTitleDisplayMode | The bar title, shown large or inline. |
toolbarBackground, toolbarBackgroundVisibility | Whether the bar draws its own background or lets content show through. |
toolbarColorScheme | The light or dark scheme the bar is drawn in. |
toolbarVisibility | Shows or hides the bar. |
navigationBarBackButtonHidden | Hides the back button on a pushed screen. |
searchable | Adds the search field and reports its text back. |
The Peach simulator draws each of these for the navigation bar. A bars
value other than automatic or navigationBar belongs to a tab bar or bottom
toolbar, so Peach throws instead of guessing, as it does for any other
navigation or toolbar modifier it does not yet draw.
NavigationSplitView is SwiftUI’s two or three column split view. Its direct
children are one Sidebar, an optional Content, and one Detail. Each
column accepts React Native content, one Toolbar whose entries sit inside
Toolbar.Content, and a column swiftStyle for its navigation title and
other column-level modifiers.
import { useState } from 'react'import { Text } from 'react-native'import { One } from 'one'
function Mail({ mailboxes, addMailbox }: { mailboxes: { id: string; name: string }[]; addMailbox: () => void }) { const [selection, setSelection] = useState<string[]>([])
return ( <One.iOS.NavigationSplitView style={{ flex: 1 }}> <One.iOS.NavigationSplitView.Sidebar swiftStyle={{ navigationTitleWithText: 'Mailboxes' }} > <One.iOS.Toolbar> <One.iOS.List selection={selection} onSelectionChange={setSelection}> {mailboxes.map((mailbox) => ( <One.iOS.Label key={mailbox.id} label={mailbox.name} systemImage="tray" swiftStyle={{ tag: mailbox.id }} /> ))} </One.iOS.List> <One.iOS.Toolbar.Content> <One.iOS.ToolbarItem placement="topBarTrailing"> <One.iOS.Button label="Add" onPress={addMailbox} /> </One.iOS.ToolbarItem> </One.iOS.Toolbar.Content> </One.iOS.Toolbar> </One.iOS.NavigationSplitView.Sidebar> <One.iOS.NavigationSplitView.Detail> <Text>Select a mailbox.</Text> </One.iOS.NavigationSplitView.Detail> </One.iOS.NavigationSplitView> )}Omit columnVisibility and preferredCompactColumn to let SwiftUI manage
them. Supplying either prop makes it controlled: its callback reports the
native proposal, and the prop remains authoritative until React updates it.
columnVisibility is automatic, all, doubleColumn, or detailOnly;
preferredCompactColumn is sidebar, content, or detail and applies on
iOS 17 or later.
| Props | Type | Description |
|---|---|---|
columnVisibilityNavigationSplitViewVisibility = "automatic"The controlled visibility of the columns. | NavigationSplitViewVisibility = "automatic" | The controlled visibility of the columns. |
onColumnVisibilityChange(visibility: NavigationSplitViewVisibility) => voidReceives a native visibility proposal. | (visibility: NavigationSplitViewVisibility) => void | Receives a native visibility proposal. |
preferredCompactColumnNavigationSplitViewColumn = "sidebar"The controlled compact column. Applies on iOS 17 or later. | NavigationSplitViewColumn = "sidebar" | The controlled compact column. Applies on iOS 17 or later. |
onPreferredCompactColumnChange(column: NavigationSplitViewColumn) => voidReceives a native compact-column proposal. | (column: NavigationSplitViewColumn) => void | Receives a native compact-column proposal. |
children (required)ReactNodeA Sidebar, optional Content, and Detail column. | ReactNode | A Sidebar, optional Content, and Detail column. |
Toolbar becomes one native toolbar. In a NavigationStack or TabView, its
children are ToolbarItem, ToolbarItemGroup, or ToolbarSpacer elements in
order. A split view column uses Toolbar as a content wrapper and puts those
entries inside Toolbar.Content. NavigationStack merges every toolbar it is
given, and One.iOS.TabView also takes a Toolbar child.
import { One } from 'one'
function SaveToolbar({ save }: { save: () => void }) { return ( <One.iOS.Toolbar> <One.iOS.ToolbarItem placement="primaryAction"> <One.iOS.Button label="Save" onPress={save} /> </One.iOS.ToolbarItem> </One.iOS.Toolbar> )}| Props | Type | Description |
|---|---|---|
children (required)ReactNodeToolbar entries, or column content with a Toolbar.Content marker. | ReactNode | Toolbar entries, or column content with a Toolbar.Content marker. |
Toolbar.Content marks the toolbar entries inside a split view column’s
content wrapper. Its children follow the same order and types as a regular
toolbar: one Toolbar.Content per toolbar, and it needs children.
ToolbarItem places one item in the bar. It needs children.
import { One } from 'one'
function EditItem({ edit }: { edit: () => void }) { return ( <One.iOS.ToolbarItem placement="topBarTrailing"> <One.iOS.Button label="Edit" onPress={edit} /> </One.iOS.ToolbarItem> )}| Props | Type | Description |
|---|---|---|
placementToolbarItemPlacement = "automatic"Where the item goes. See below. | ToolbarItemPlacement = "automatic" | Where the item goes. See below. |
children (required)ReactNodeThe item, such as a One.iOS.Button. | ReactNode | The item, such as a One.iOS.Button. |
ToolbarItemPlacement is every SwiftUI placement: automatic, principal,
navigation, primaryAction, secondaryAction, status,
confirmationAction, cancellationAction, destructiveAction, keyboard,
topBarLeading, topBarTrailing, topBarPinnedTrailing,
navigationBarLeading, navigationBarTrailing, title, largeTitle,
bottomBar, subtitle, and largeSubtitle. A placement newer than the
running iOS throws.
ToolbarItemGroup places several items together. It needs children, and a
systemImage requires a label.
import { One } from 'one'
function PageControls({ back, forward }: { back: () => void; forward: () => void }) { return ( <One.iOS.ToolbarItemGroup placement="bottomBar" label="Pages"> <One.iOS.Button label="Back" onPress={back} /> <One.iOS.Button label="Forward" onPress={forward} /> </One.iOS.ToolbarItemGroup> )}| Props | Type | Description |
|---|---|---|
placementToolbarItemPlacement = "automatic"Where the group goes. | ToolbarItemPlacement = "automatic" | Where the group goes. |
labelstring = ""Names the group when SwiftUI collapses it. | string = "" | Names the group when SwiftUI collapses it. |
systemImageSFSymbolName | "" = ""An SF Symbol with the label. Needs a label. | SFSymbolName | "" = "" | An SF Symbol with the label. Needs a label. |
children (required)ReactNodeThe items. | ReactNode | The items. |
DefaultToolbarItem places one of the system’s own toolbar items, such as the search
field, where you say. It needs iOS 26 and takes no children. With
kind="search" and placement="bottomBar" on a list that is searchable, plus
searchToolbarBehavior: 'minimize' in the list’s swiftStyle, the field becomes the
detached search circle iPhone shows at the bottom of the screen.
import { One } from 'one'
function BottomSearchToolbar() { return ( <One.iOS.Toolbar> <One.iOS.DefaultToolbarItem kind="search" placement="bottomBar" /> <One.iOS.ToolbarSpacer sizing="flexible" placement="bottomBar" /> </One.iOS.Toolbar> )}| Props | Type | Description |
|---|---|---|
kind (required)ToolbarDefaultItemKindWhich system item to place, such as search. | ToolbarDefaultItemKind | Which system item to place, such as search. |
placementToolbarItemPlacementWhere in the bars the item goes. | ToolbarItemPlacement | Where in the bars the item goes. |
ToolbarSpacer spreads toolbar items apart. It needs iOS 26 and takes no
children.
import { One } from 'one'
function SpacedToolbar({ save }: { save: () => void }) { return ( <One.iOS.Toolbar> <One.iOS.ToolbarItem placement="topBarLeading"> <One.iOS.Button label="Save" onPress={save} /> </One.iOS.ToolbarItem> <One.iOS.ToolbarSpacer sizing="flexible" /> </One.iOS.Toolbar> )}| Props | Type | Description |
|---|---|---|
sizingSpacerSizing = "flexible"flexible fills the space; fixed adds a standard gap. | SpacerSizing = "flexible" | flexible fills the space; fixed adds a standard gap. |
placementToolbarItemPlacement = "automatic"The bar the spacer sits in. | ToolbarItemPlacement = "automatic" | The bar the spacer sits in. |
| iOS | Android | Web | |
|---|---|---|---|
| Renders | SwiftUI chrome | Throws | Throws |
| Root | Native content | None | None |
| Columns | Split columns | None | None |
ToolbarItem, ToolbarItemGroup, or
ToolbarSpacer directly in a NavigationStack or split view column throws,
as does a Toolbar.Content outside its Toolbar.Toolbar, entry, or column
marker anywhere but its parent throws.Sidebar and one
Detail, at most one Content, one Toolbar per column with at most one
Toolbar.Content, and content in every column.ToolbarSpacer needs iOS 26.Edit this page on GitHub.