The native header

A screen that wants a navigation bar declares <native-header> in its template:

Import
import {
  NativeHeader,
  NativeHeaderItem,
  NativeSearchBar,
  NativeToolbarItem,
  ScreenSafeAreaView,
} from '@ng-native/router';
Template
<native-header><native-header-item><native-search-bar><native-toolbar-item><screen-safe-area-view> once they are in the component's imports
native-headercomponent@ng-native/router
import { NativeHeader } from '@ng-native/router';and add NativeHeader to the component's imports

Inputs

title
string

The bar's title. Absent leaves whatever the platform would show, which is nothing.

titleColor
string | number

Title colour.

titleFontFamily
string

titleFontSize
unknown

titleFontWeight
string

A CSS font weight, '600' or 'bold' .

liquidGlass
unknown

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" .

backgroundColor
string | number

The bar's background. Ignored unless blurEffect is none .

color
string | number

Tint colour: the back chevron, and any header item that does not set its own.

blurEffect
BlurEffect

iOS blur material behind the bar.

hidden
unknown

Hide the bar entirely, keeping the screen's gestures and safe areas.

tabBarHidden
unknown

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.

hideShadow
unknown

Drop the hairline under the bar.

translucent
unknown

Let content scroll under the bar rather than starting below it.

direction
HeaderDirection

Lay the bar out right to left.

topInsetEnabled
unknown

Android: add the status bar inset to the bar's height.

userInterfaceStyle
HeaderInterfaceStyle

Force the bar light or dark regardless of the app's appearance.

largeTitle
unknown

iOS large title, which collapses into the bar as content scrolls.

largeTitleColor
string | number

largeTitleBackgroundColor
string | number

largeTitleFontFamily
string

largeTitleFontSize
unknown

largeTitleFontWeight
string

largeTitleHideShadow
unknown

backTitle
string

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.

backTitleFontFamily
string

backTitleFontSize
unknown

backTitleVisible
unknown

Show the back button's title at all. Native defaults this to true.

backButtonDisplayMode
BackButtonDisplayMode

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.

backButtonInCustomView
unknown

Put the back button in the custom view slot, so a back header item can replace it.

disableBackButtonMenu
unknown

Drop the long-press menu of the screens below.

hideBackButton
unknown

Hide the back button without disabling the swipe-back gesture.

consumeTopInset
unknown = nativePlatform() === 'android'

Android: whether the bar takes the status bar's height as padding, so its title sits below it rather than under it.

consumeLeftInset
unknown

consumeRightInset
unknown

consumeBottomInset
unknown

@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/router
import { NativeHeaderItem } from '@ng-native/router';and add NativeHeaderItem to the component's imports

Inputs

type
HeaderItemType

Which slot to sit in. Native defaults to left .

hidesSharedBackground
unknown

Opt out of the bar's shared background behind this item.

title
string

systemImageName
string

An SF Symbol name.

tintColor
string

barButtonItemStyle
'plain' | 'prominent'

prominent is the filled style iOS 26 gives a screen's main action.

sharesBackground
unknown

iOS 26: whether this item shares its glass with the items beside it, which it does unset.

hidden
unknown

Leave the item out of the bar.

disabled
unknown

accessibilityLabel
string

What a screen reader says for a button with a symbol and no title, or for an item's view.

menu
HeaderMenu

What a tap on the bar button opens, instead of sending (press) .

spacing
unknown

A gap of this many points in the bar, with no button. iOS only, as bar buttons are.

Outputs

press
void

The bar button was tapped. Content written in the item handles its own presses.

menuSelect
string

An action of the menu was tapped: its id .

<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/router
import { NativeSearchBar } from '@ng-native/router';and add NativeSearchBar to the component's imports

Inputs

placeholder
string

hideWhenScrolling
unknown

Scroll the field away with the content, as iOS lists do by default.

cancelButtonText
string

placement
SearchBarPlacement

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.

allowToolbarIntegration
unknown

iOS 26: whether automatic may move the field into the toolbar.

autoCapitalize
'systemDefault' | 'none' | 'words' | 'sentences' | 'characters'

obscureBackground
unknown

Dim the content behind the field while it is being typed in.

hideNavigationBar
unknown

Hide the navigation bar while the field is being typed in.

tintColor
string

iOS: the cursor and the Cancel button. Android: the field's background.

textColor
string

The typed text.

Two-way

query
string = ''

Outputs

search
string

The keyboard's search key was pressed, with the text in the field.

cancel
void

The Cancel button was pressed.

searchFocus
void

searchBlur
void

Methods

clear(): void

Empty the field, and the query with it.

cancelSearch(): void

Close 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/router
import { NativeToolbarItem } from '@ng-native/router';and add NativeToolbarItem to the component's imports

Inputs

type
ToolbarItemType

title
string

systemImageName
string

An SF Symbol name.

tintColor
string

barButtonItemStyle
'plain' | 'prominent'

prominent is the filled style iOS 26 gives a screen's main action.

hidesSharedBackground
unknown

iOS 26: draw this item with no glass behind it.

sharesBackground
unknown

iOS 26: whether this item shares its glass with the items beside it, which it does unset.

width
unknown

The width of a fixedSpacer , in points.

hidden
unknown

Leave the item out of the toolbar while keeping its place among the others.

disabled
unknown

accessibilityLabel
string

What a screen reader says for a button with a symbol and no title.

Outputs

press
void

The 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/router
import { ScreenSafeAreaView } from '@ng-native/router';and add ScreenSafeAreaView to the component's imports

Inputs

edges
readonly 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.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.