The native header
A screen that wants a navigation bar declares <native-header> in its template:
import {
NativeHeader,
NativeHeaderItem,
NativeSearchBar,
NativeToolbarItem,
ScreenSafeAreaView,
} from '@ng-native/router';
<native-header><native-header-item><native-search-bar><native-toolbar-item><screen-safe-area-view> once they are in the component's importsnative-headercomponent@ng-native/routerimport { NativeHeader } from '@ng-native/router';and add NativeHeader to the component's importsInputs
titlestringThe bar's title. Absent leaves whatever the platform would show, which is nothing.
titleColorstring | numberTitle colour.
titleFontFamilystringtitleFontSizeunknowntitleFontWeightstring A CSS font weight, '600' or 'bold' .
liquidGlassunknown On iOS 26 and later, the Liquid Glass navigation bar for this header, where it has no background of its own: clear and unlined, with the content scrolling under it. false keeps the neutral bar on a page of an app whose withHeaderDefaults asks for the glass one. The bar is over the page, so its scroll view takes contentInsetAdjustmentBehavior="automatic" .
backgroundColorstring | number The bar's background. Ignored unless blurEffect is none .
colorstring | numberTint colour: the back chevron, and any header item that does not set its own.
blurEffectBlurEffectiOS blur material behind the bar.
hiddenunknownHide the bar entirely, keeping the screen's gestures and safe areas.
tabBarHiddenunknown Hide the tab bar while this page is the one in front of its tab, as UIKit's hidesBottomBarWhenPushed does: on a detail page pushed in a tab's stack, say. The bar goes as the page is pushed and comes back as it is popped, without UIKit's slide. Outside a tab it does nothing, and <native-tabs-outlet [barHidden]> overrides it when bound.
hideShadowunknownDrop the hairline under the bar.
translucentunknownLet content scroll under the bar rather than starting below it.
directionHeaderDirectionLay the bar out right to left.
topInsetEnabledunknownAndroid: add the status bar inset to the bar's height.
userInterfaceStyleHeaderInterfaceStyleForce the bar light or dark regardless of the app's appearance.
largeTitleunknowniOS large title, which collapses into the bar as content scrolls.
largeTitleColorstring | numberlargeTitleBackgroundColorstring | numberlargeTitleFontFamilystringlargeTitleFontSizeunknownlargeTitleFontWeightstringlargeTitleHideShadowunknownbackTitlestring What *this* screen's back button says. RNSScreenStackHeaderConfig.mm writes it onto the previous screen's navigation item, falling back to that screen's title, which is why an unset back title reads as the title of the screen you came from.
backTitleFontFamilystringbackTitleFontSizeunknownbackTitleVisibleunknownShow the back button's title at all. Native defaults this to true.
backButtonDisplayModeBackButtonDisplayMode iOS 14+: how aggressively the back title is shortened when space is tight. When iOS draws the button as the chevron alone ( minimal without a back title font or disableBackButtonMenu , or [backTitleVisible]="false" ), VoiceOver reads it as "Back" rather than the back title: UIKit labels it itself.
backButtonInCustomViewunknown Put the back button in the custom view slot, so a back header item can replace it.
disableBackButtonMenuunknownDrop the long-press menu of the screens below.
hideBackButtonunknownHide the back button without disabling the swipe-back gesture.
consumeTopInsetunknown = nativePlatform() === 'android'Android: whether the bar takes the status bar's height as padding, so its title sits below it rather than under it.
consumeLeftInsetunknownconsumeRightInsetunknownconsumeBottomInsetunknown@Component({
selector: 'x-detail',
imports: [NativeHeader, NativeHeaderItem /* ... */],
template: `
<native-header title="Detail" backTitle="Back" [largeTitle]="true">
<native-header-item type="right">
<pressable (press)="star()"><text>★</text></pressable>
</native-header-item>
</native-header>
<!-- the rest of the screen -->
`,
})
export class Detail {}The header can go anywhere in the screen: at the top level, inside a <safe-area-view> or a
@if, or in a layout component the page renders. Native only looks for it among the screen's
direct children, so that is where it is committed: just after the part of the page it was written
in, or after the rest of the page when it was written at the top level. Everything else about it,
its bindings and its @if included, behaves where you wrote it. Write one header per screen.
Header items work the same way inside the header. A <native-header-item> can sit in a component
you put in the header, such as a set of actions several pages share, and is still committed as a
child of the header, in the order it was written. The header takes nothing but its items.
Large titles
A large title collapses into the bar as the page scrolls when the page's scroll view comes first in the screen, with nothing wrapped around or ahead of it, and adjusts its insets for the bar:
<native-header title="Library" [largeTitle]="true" />
<scroll-view contentInsetAdjustmentBehavior="automatic">
<!-- ... -->
</scroll-view><virtual-list> takes the same input. Put anything that belongs above the rows, such as a row of
filters, in its listHeader rather than ahead of the list. On iOS a header with a large title is
translucent, with a clear, unlined bar at the top of the content (largeTitleBackgroundColor is
transparent and largeTitleHideShadow is true), which is how UIKit draws one; binding any of
those, or setting them in withHeaderDefaults, takes precedence.
On iOS a stack whose root page has no header keeps its bar hidden, and react-native-screens turns
large titles on only once a page with one is being pushed. Before that first push, the header has
the native half of @ng-native/expo lay the hidden bar out with a large title, so the push starts
at the height it ends at. That needs the package installed and a new development build. In Expo
Go the first such push starts from the compact bar: the inline title fades out and the large
title drops into place. Later pushes are right either way.
The same package keeps a header item from laying the bar out while a push or pop runs, which
react-native-screens does each time an item's size changes. A <ui-host [matchContents]="true"> in
an item has its size only once SwiftUI lays it out, during the push. In Expo Go, which cannot load
the package, a large title pushed with such an item onto a stack whose root page has no header
starts above its place on every push and drops into it: give the host a fixed size there, or use
systemImageName for an SF Symbol button.
A header an @if takes away leaves the screen with no navigation bar, the same as a screen that
never had one, and a header that comes back brings its bar back. Native keeps the bar it was last
told about until a header config says otherwise, so once a screen has had a header it keeps one
config for the rest of its life: removing the header commits that config as hidden, and the next
header on the screen takes it over. [hidden] does the same for a bar that should go and come
back with the same header in place.
tabBarHidden hides the tab bar, rather than the navigation bar, while the page is in front of
its tab: see
hiding the bar on a pushed page.
<native-header-item> places arbitrary Angular content - not just text - into one of the bar's
slots: left, center/title, right, back (replaces the chevron; needs
backButtonInCustomView on the header), or searchBar. title is committed as center, the
one spelling Android's header takes. Press handling is the ordinary responder system, so a
pressable inside one works like anywhere else.
Bar buttons
On iOS, an item at the left or the right with nothing written in it and a systemImageName or
a title is a native bar button rather than a view, drawn by UIKit as a UIBarButtonItem, as the
system's own buttons are:
<native-header title="New property">
<native-header-item
type="left"
systemImageName="xmark"
accessibilityLabel="Close"
(press)="close()"
/>
<native-header-item
type="right"
title="Save"
barButtonItemStyle="prominent"
[disabled]="saving()"
(press)="save()"
/>
</native-header>Its inputs are <native-toolbar-item>'s. systemImageName is an SF Symbol, sized and tinted by
the bar as the system's own buttons are, and a button with a symbol and no title needs an
accessibilityLabel for VoiceOver to say. barButtonItemStyle="prominent" is the filled style iOS
26 gives a screen's main action, tintColor overrides the header's color for one button, and
disabled does what it says. On iOS 26 the buttons beside one another share one glass
background: hidesSharedBackground draws a button with none, and [sharesBackground]="false"
gives it glass of its own. (press) is a tap on the button.
Bar buttons and items with content can share a side, in the order they are written. An item with
anything written in it, an @if included, stays a view. hidden leaves any item out of the bar.
Android's header has no bar buttons: it draws nothing for one, and says so once in development.
Bar button menus
A bar button with a [menu] opens a native UIMenu on a tap, instead of sending (press), and
(menuSelect) reports the id of the action chosen. It still needs a systemImageName or a
title to draw:
import { Component, computed, signal } from '@angular/core';
import { NativeHeader, NativeHeaderItem, type HeaderMenu } from '@ng-native/router';
@Component({
imports: [NativeHeader, NativeHeaderItem],
template: `
<native-header title="Notes">
<native-header-item
type="right"
systemImageName="ellipsis"
accessibilityLabel="More"
[menu]="more()"
(menuSelect)="run($event)"
/>
</native-header>
`,
})
export class Notes {
protected readonly byDate = signal(true);
protected readonly more = computed((): HeaderMenu => ({
items: [
{
type: 'submenu',
title: 'Sort',
systemImageName: 'arrow.up.arrow.down',
items: [
{ type: 'action', id: 'date', title: 'Date', state: this.byDate() ? 'on' : 'off' },
{ type: 'action', id: 'name', title: 'Name', state: this.byDate() ? 'off' : 'on' },
],
},
{ type: 'action', id: 'delete', title: 'Delete', destructive: true },
],
}));
protected run(id: string): void {
if (id === 'date' || id === 'name') this.byDate.set(id === 'date');
}
}An action takes systemImageName, state (on, off or mixed), disabled, destructive and
hidden. A submenu takes systemImageName, destructive, singleSelection and displayInline,
which draws its items as a section of the menu rather than behind a row of their own. Any change to
a side's bar buttons, a menu's included, builds that side's items again, so a change made while a
menu is open can close it.
Spacing bar buttons
An item with a [spacing] is a fixed gap of that many points, with no button, where it is written
among the side's items, and hidden takes it away. Android draws nothing for it, as for a bar
button:
<native-header-item type="right" systemImageName="plus" accessibilityLabel="Add" (press)="add()" />
<native-header-item type="right" [spacing]="12" />
<native-header-item type="right" title="Done" (press)="done()" />The header has no flexible spacer: UIKit sets the sides of a navigation bar apart itself.
native-header-itemcomponent@ng-native/routerimport { NativeHeaderItem } from '@ng-native/router';and add NativeHeaderItem to the component's importsInputs
typeHeaderItemType Which slot to sit in. Native defaults to left .
hidesSharedBackgroundunknownOpt out of the bar's shared background behind this item.
titlestringsystemImageNamestringAn SF Symbol name.
tintColorstringbarButtonItemStyle'plain' | 'prominent'prominent is the filled style iOS 26 gives a screen's main action.
sharesBackgroundunknowniOS 26: whether this item shares its glass with the items beside it, which it does unset.
hiddenunknownLeave the item out of the bar.
disabledunknownaccessibilityLabelstringWhat a screen reader says for a button with a symbol and no title, or for an item's view.
menuHeaderMenu What a tap on the bar button opens, instead of sending (press) .
spacingunknownA gap of this many points in the bar, with no button. iOS only, as bar buttons are.
Outputs
pressvoidThe bar button was tapped. Content written in the item handles its own presses.
menuSelectstring An action of the menu was tapped: its id .
Search
<native-search-bar> is the navigation bar's own search field: react-native-screens'
RNSSearchBar, the UISearchController iOS puts under a large title. Use it for a list that
filters from the bar, as the notes, wallet and music examples do, rather than a text field at the
top of the page. It goes in the searchBar slot. For a search field where there is no navigation
bar, such as in a bottom sheet, use <search-field>.
native-search-barcomponent@ng-native/routerimport { NativeSearchBar } from '@ng-native/router';and add NativeSearchBar to the component's importsInputs
placeholderstringhideWhenScrollingunknownScroll the field away with the content, as iOS lists do by default.
cancelButtonTextstringplacementSearchBarPlacement Where iOS puts the field. automatic lets iOS 26 fold it into a toolbar, which a presented sheet does not have, so there it shows nowhere; stacked keeps it under the title.
allowToolbarIntegrationunknown iOS 26: whether automatic may move the field into the toolbar.
autoCapitalize'systemDefault' | 'none' | 'words' | 'sentences' | 'characters'obscureBackgroundunknownDim the content behind the field while it is being typed in.
hideNavigationBarunknownHide the navigation bar while the field is being typed in.
tintColorstringiOS: the cursor and the Cancel button. Android: the field's background.
textColorstringThe typed text.
Two-way
querystring = ''Outputs
searchstringThe keyboard's search key was pressed, with the text in the field.
cancelvoidThe Cancel button was pressed.
searchFocusvoidsearchBlurvoidMethods
clear(): voidEmpty the field, and the query with it.
cancelSearch(): voidClose the search, as the Cancel button does.
import { Component, computed, signal } from '@angular/core';
import { ScrollView, Text } from '@ng-native/components';
import { NativeHeader, NativeHeaderItem, NativeSearchBar } from '@ng-native/router';
@Component({
selector: 'x-notes',
imports: [NativeHeader, NativeHeaderItem, NativeSearchBar, ScrollView, Text],
template: `
<native-header title="Notes" [largeTitle]="true">
<native-header-item type="searchBar">
<native-search-bar placeholder="Search notes" [(query)]="query" (search)="save($event)" />
</native-header-item>
</native-header>
<scroll-view contentInsetAdjustmentBehavior="automatic">
@for (note of shown(); track note) {
<text>{{ note }}</text>
}
</scroll-view>
`,
})
export class Notes {
protected readonly query = signal('');
protected readonly recent = signal<string[]>([]);
private readonly notes = ['Groceries', 'Garden plan', 'Trip to Lisbon'];
protected readonly shown = computed(() =>
this.notes.filter((note) => note.toLowerCase().includes(this.query().toLowerCase())),
);
protected save(text: string): void {
this.recent.update((recent) => [text, ...recent]);
}
}The text is a two-way query. Typing updates it, and a query set from code, such as a recent
search tapped or a suggestion taken, is put in the field the way a controlled text input's value
is. That write happens after the render, so a query the page starts with, as a screen restored
with its search would have, reaches a field that has already been committed. What the user typed
is not sent back to the field.
(search) is the keyboard's search key, with the text in the field; (cancel) is the Cancel
button; (searchFocus) and (searchBlur) are the field taking and leaving the keyboard, which is
when a suggestions list should come and go. From code, focus(), blur(), clear() and
cancelSearch() do the same, through a viewChild(NativeSearchBar). clear() empties the query
along with the field.
Every other input is left unset unless bound, so the platform's own default applies:
placeholder, cancelButtonText, hideWhenScrolling (scroll the field away with the content, as
iOS lists do by default), autoCapitalize, obscureBackground (dim the content while typing),
hideNavigationBar (hide the bar while typing), tintColor (on iOS the cursor and the Cancel
button, on Android the field's background) and textColor (the typed text).
placement and allowToolbarIntegration decide where iOS puts the field. The default,
automatic, lets iOS 26 fold it into a toolbar, and a presented sheet has none, so there the
field shows nowhere; placement="stacked" keeps it under the title. react-native-screens'
Android-only search bar props (autoFocus, inputType, hintTextColor and the rest) are not
inputs here.
On iOS 27 the field is drawn as white glass, where SwiftUI's
.searchable(placement: .navigationBarDrawer(displayMode: .always)) draws a flat grey field
(tertiarySystemFill).
Both are a UISearchBar in the minimal style; UIKit turns the glass material on for the one
react-native-screens' search controller puts in the navigation bar and off for SwiftUI's, and no
public UIKit API or react-native-screens prop turns it off, so <native-search-bar> has no input
that matches SwiftUI's field. On iOS, tintColor and textColor leave the field's fill alone, and
react-native-screens' barTintColor, which sets the text field's background color, only tints the
glass, so it is not an input here either.
The field and its placeholder are drawn in the system font, on iOS and Android, whatever the
header's titleFontFamily or the page's CSS says: react-native-screens' search bar has no font
prop on either platform, so <native-search-bar> has no font input, and a font-family or
font-size on it changes nothing. For a search field in the app's own font, put a
<search-field> under the header instead, which takes the font
its CSS resolves to. There is no scope bar and there are no search tokens either, because
react-native-screens has neither: a scope is a segmented control under the header, and a token is
text.
In a test, the field is found by its view name, fabric.find('RNSSearchBar'), and takes typing
from fireEvent.changeText or userEvent.type like a text field. The commands it sends, such as
setText for a query set from code, are on fabric.commands.
A bottom toolbar
<native-toolbar> is the screen's toolbar on iOS: the bar a navigation controller floats over the
content at the bottom of a screen, where Mail has its filter, its search field and its compose
button. It is for a screen with no tab bar. Each <native-toolbar-item> in it is a button, a
spacer, or the place the search field goes:
native-toolbar-itemcomponent@ng-native/routerimport { NativeToolbarItem } from '@ng-native/router';and add NativeToolbarItem to the component's importsInputs
typeToolbarItemTypetitlestringsystemImageNamestringAn SF Symbol name.
tintColorstringbarButtonItemStyle'plain' | 'prominent'prominent is the filled style iOS 26 gives a screen's main action.
hidesSharedBackgroundunknowniOS 26: draw this item with no glass behind it.
sharesBackgroundunknowniOS 26: whether this item shares its glass with the items beside it, which it does unset.
widthunknown The width of a fixedSpacer , in points.
hiddenunknownLeave the item out of the toolbar while keeping its place among the others.
disabledunknownaccessibilityLabelstringWhat a screen reader says for a button with a symbol and no title.
Outputs
pressvoidThe button was tapped.
import { Component, signal } from '@angular/core';
import { ScrollView, Text } from '@ng-native/components';
import {
NativeHeader,
NativeHeaderItem,
NativeSearchBar,
NativeToolbar,
NativeToolbarItem,
} from '@ng-native/router';
@Component({
selector: 'x-inbox',
imports: [
NativeHeader,
NativeHeaderItem,
NativeSearchBar,
NativeToolbar,
NativeToolbarItem,
ScrollView,
Text,
],
template: `
<native-header title="Inbox" [largeTitle]="true">
<native-header-item type="searchBar">
<native-search-bar placeholder="Search" [(query)]="query" />
</native-header-item>
</native-header>
<native-toolbar>
<native-toolbar-item
systemImageName="line.3.horizontal.decrease"
accessibilityLabel="Filter"
(press)="unread.set(!unread())"
/>
<native-toolbar-item type="searchBar" />
<native-toolbar-item
systemImageName="square.and.pencil"
accessibilityLabel="Compose"
(press)="drafts.set(drafts() + 1)"
/>
</native-toolbar>
<scroll-view contentInsetAdjustmentBehavior="automatic">
<text>{{ unread() ? 'Unread' : 'All mail' }}, {{ drafts() }} drafts</text>
</scroll-view>
`,
})
export class Inbox {
protected readonly query = signal('');
protected readonly unread = signal(false);
protected readonly drafts = signal(0);
}An item with no type is a button, drawn from systemImageName, an SF Symbol, or from title,
and (press) is a tap on it. A button with a symbol and no title needs an accessibilityLabel
for a screen reader to say. barButtonItemStyle="prominent" is the filled style iOS 26 gives a
screen's main action, and tintColor, disabled and hidden do what they say.
type="fluidSpacer" takes all the room there is, so what follows it sits at the trailing edge,
and type="fixedSpacer" is a gap of width points. type="searchBar" is where the screen's
<native-search-bar> goes once iOS 26 integrates it into the toolbar, which puts buttons either
side of the field. On iOS 26 the items beside one another share one glass background:
hidesSharedBackground draws an item with none, and [sharesBackground]="false" gives it glass of
its own.
react-native-screens has no toolbar, so these commit as Expo Router's native toolbar views. An app
that uses them installs expo-router for that native half, with npx expo install expo-router.
None of its JavaScript is imported, and Expo Go has it built in. Without it the elements commit as
nothing (UnimplementedNativeView).
The toolbar needs iOS 18, and the search field's place in it needs iOS 26. It is the toolbar of the screen it is written in, wherever in the page that is, and it goes when the element does. A presented screen has no navigation controller, so no toolbar. Android has no such bar, and draws nothing for either element.
A screen inside a tab bar is not the place for one. From iOS 26, UIKit draws a navigation
controller's toolbar on the same row as the tab bar and behind it, so its items show only where the
tab bar does not cover them: a button at each end poking out from under it. That is UIKit's own
layout, and the same in an app written in Swift. In a tab, put a screen's actions in the header
with a <native-header-item> at the left or the right, its search in the header's
search field, or give search a tab of its own with
<native-tab systemItem="search">.
Colors
A header prop cannot read the cascade, so left alone each platform chooses for itself: iOS follows
the system appearance, and Android takes the app theme's colorPrimary - the framework blue on
every screen. NativeHeader closes that gap by defaulting backgroundColor, color and
titleColor to a neutral background/foreground pair for the current color scheme, so a bar
matches the screen under it without anything bound. NATIVE_HEADER_PALETTE overrides those
defaults for an app that retunes its palette:
providers: [
{
provide: NATIVE_HEADER_PALETTE,
useValue: {
light: { background: '#ffffff', foreground: '#0a0a0a' },
dark: { background: '#0a0a0a', foreground: '#fafafa' },
},
},
],Binding backgroundColor, color or titleColor on a particular <native-header> still wins
over the palette - the token only fills in what a call site left unset.
Liquid Glass on iOS 26
A bar with a background is opaque: the content stops at it, with a hairline under it. Since iOS 26
the system's own navigation bar is clear instead, with the content scrolling under it and blurring
out and the buttons floating over it. withHeaderDefaults asks for that bar for the whole app:
provideNativeRouter(routes, withHeaderDefaults({ liquidGlass: true }));Every <native-header> with no background of its own is then clear, translucent and unlined on iOS
26 and later. One header asks for it, or refuses it, with its own liquidGlass input, which wins
over the app's: <native-header title="Orders" [liquidGlass]="true" /> on the one page built for
it, or [liquidGlass]="false" on a page that is not. Before iOS 26 and on Android it is the neutral
bar above, and a header that binds backgroundColor, or defaults that give one, keep it.
The bar is over the page rather than above it, so the page is built for that: its scroll view is
the page's body and takes contentInsetAdjustmentBehavior="automatic", which starts the content
below the bar and lets it scroll under. A page with anything above its scroll view, or with no
scroll view, starts behind the bar. Wrap that page in a <screen-safe-area-view>, or give its
header a backgroundColor, which turns the glass off for that page.
A page under the bar
<screen-safe-area-view> insets its content by what covers the screen it is on. Under a clear bar
that is the bar, the search bar under it and the status bar, so a row of controls above a list, a
toolbar above a map or a centred empty state starts where UIKit would put it, and the bar stays
glass:
<native-header title="Planner" />
<screen-safe-area-view [edges]="['top']" style="flex: 1">
<view class="controls"><text>Schedule</text></view>
<scroll-view style="flex: 1"><!-- rows --></scroll-view>
</screen-safe-area-view>screen-safe-area-viewcomponent@ng-native/routerimport { ScreenSafeAreaView } from '@ng-native/router';and add ScreenSafeAreaView to the component's importsInputs
edgesreadonly ScreenSafeAreaEdge[]Which edges to inset. Absent insets all four.
<safe-area-view> cannot do this: its insets come from the app's <safe-area-provider>, at the
root, which knows the status bar and not the navigation bar. This one asks the screen. It works on
any page of a stack and on a tab, for any edge, and follows the bar as it changes. The inset is
applied as margin.
On Android the bar is above the page rather than over it, so under a header that is shown and not
translucent the top edge adds nothing, and the same markup is right on both platforms. With the
header hidden it insets by the status bar.
<tab-safe-area-view> is the same view under the name it has for
content above the tab bar.
Defaults for every header
An app whose bars should match its pages would otherwise repeat the same bindings on every
<native-header>. withHeaderDefaults says them once, beside the routes:
import { provideNativeRouter, withHeaderDefaults } from '@ng-native/router';
import { routes } from './routes.ts';
export const providers = [
provideNativeRouter(
routes,
withHeaderDefaults((scheme) => ({
backgroundColor: scheme === 'dark' ? '#101014' : '#f4f4f7',
titleColor: scheme === 'dark' ? '#ffffff' : '#101014',
largeTitleColor: scheme === 'dark' ? '#ffffff' : '#101014',
color: '#3b6ef5',
userInterfaceStyle: scheme,
hideShadow: true,
})),
),
];Pass an object for defaults that never change, or a function of the color scheme - 'light' or
'dark', from ColorScheme in @ng-native/device - for ones that follow the system appearance,
the way a page's @media (prefers-color-scheme: dark) rules do. The function runs inside a
computed, so a signal it reads, such as an in-app theme setting, is followed too.
The defaults cover the header's appearance: userInterfaceStyle, backgroundColor, color,
blurEffect, hideShadow, translucent, the title* and largeTitle* colors and fonts,
largeTitleHideShadow, backTitleFontFamily, backTitleFontSize and backButtonDisplayMode.
On iOS, backButtonDisplayMode has no effect on a header that also sets backTitleFontFamily,
backTitleFontSize or disableBackButtonMenu, in a screen or in the defaults. Those make
react-native-screens build its own titled back button, and UIKit then shows the previous screen's
title whatever the display mode says, so minimal does not reduce the button to the chevron.
[backTitleVisible]="false" is not affected: it keeps the chevron alone with those set too.
On iOS, VoiceOver reads a back button that iOS draws as the chevron alone, through
backButtonDisplayMode="minimal" (with none of the options above) or [backTitleVisible]="false",
as "Back", not as the previous screen's title or backTitle. UIKit labels that button itself, and
react-native-screens has no prop that changes the label. A screen whose back button should be read
as the previous screen's title keeps the default display mode, which shows the title beside the
chevron.
What a screen says about itself - its title, whether it has a large title, whether the bar is hidden - stays on the screen.
Each <native-header> still overrides: anything it binds, including an explicit false, wins
over the defaults. A color the defaults leave out falls back to NATIVE_HEADER_PALETTE.
Insets
A header owns the top safe-area inset, so a screen that has one should not also wrap itself in a
<safe-area-view> - that would clear the notch twice. And a screen opened with present() has no
header at all, because it sits outside the navigation controller the header configures; see
Screens and navigation for what such a screen owns instead.