Skip to main content
‹ Screen options
Class

ScreenOptions

The options of a screen as a chain: the modifiers of OptionsChain and those that belong to one screen only, such as its title and buttons.

SectionScreen optionsDraftDocumented, not reviewed yet: the API can change.
Import
import { ScreenOptions } from '@apexnative/navigation';

ScreenOptions is the chain that builds the options of a screen: its title, its buttons, its bars, the back gesture, the status bar. You don't create it: every place that takes options as a function hands you one, usually named s, and you return it with your steps added.

options: (s) => s.title('Profile').largeTitle().hideBottomTabs(),

It is the s of defineScreen({ options }), of options in createNavigation, in groups and in presentation, of the options of a command (push, showModal…), of Screen(…).options(), of setOptions and of useScreenOptions.

Usage​

src/screens/Profile.tsx
import { defineScreen } from '@apexnative/navigation';

function Profile({ userId }: { userId: string }) {
// …
}

export const ProfileScreen = defineScreen({
component: Profile,
options: (s, props) =>
s
.title('Profile')
.largeTitle()
.rightButtons({
id: 'edit',
text: 'Edit',
onPress: ({ navigation }) =>
navigation.push('EditProfile', {
props: { userId: props.userId },
}),
}),
});

Profile shows with a large title on iOS (a regular one on Android) and an Edit button that opens EditProfile. The options go to the native side with the command that shows the screen, so the bar is complete in the first frame: no title pops in after React renders.

How it works:

  • Each step returns a new chain, with its options merged over those of the steps before. The last one wins.
  • Objects merge key by key, the rest replaces: title then subtitle keeps both, a second rightButtons(…) replaces the buttons of the first. undefined keeps what was there.
  • The steps are shortcuts of the Options object: title('Profile') is { topBar: { title: { text: 'Profile' } } }. options() takes any option the steps don't name.

Signature​

class ScreenOptions extends OptionsChain
Defined in src/api/options/ScreenOptions.ts ↗

The function that gets the chain gets two more arguments in defineScreen, a group or a command: the props of the screen, and { theme, context }. The app's defaults have no props: (s, { theme, context }). setOptions and useScreenOptions get the chain alone; a component has its props and theme at hand.

Title​

title​

title(title: string | OptionsTopBarTitle): this
PlatformsiOSiPadOSAndroid

The title in the top bar. A text, or an object with its look: color, fontSize, fontWeight, fontFamily, or a React component in place of the text.

s.title('Inbox');
s.title({ text: 'Inbox', color: '#0A84FF' });

The title of the previous screen is also what iOS shows on the back button, unless backButton says otherwise. See The title.

subtitle​

subtitle(subtitle: string | OptionsTopBarSubtitle): this
PlatformsiOSiPadOSAndroid

A second line under the title: a text, or an object with its look.

s.title('Chat').subtitle('online');

largeTitle​

largeTitle(largeTitle: boolean | OptionsTopBarLargeTitle = true): this
PlatformsiOSiPadOSAndroid (not supported)

iOS The large title over the content, which shrinks into the bar as the list scrolls. largeTitle() turns it on, largeTitle(false) off; an object sets its look.

s.title('Settings').largeTitle();

Android ignores it. With a large title, the screen leaves the top edge out of its safe area by itself. See The large title.

Top bar​

topBar​

topBar(topBar: OptionsTopBar): this
PlatformsiOSiPadOSAndroid

Any option of the top bar, for those the steps don't name: its background, its border, drawBehind, the title menu.

s.topBar({ noBorder: true, background: { color: '#F2F2F7' } });

It merges with the other top bar steps, in order. Every key: Top bar options.

hideTopBar​

hideTopBar(hidden = true): this
PlatformsiOSiPadOSAndroid

Hides the top bar of this screen; hideTopBar(false) shows it. The value can come from state, in useScreenOptions:

useScreenOptions((s) => s.hideTopBar(fullScreen));

The bar of the screen under it comes back when the user goes back. Without a bar there is no back button: give the user a way back on screen. See Hiding the bar.

backButton​

backButton(backButton: OptionsTopBarBackButton): this
PlatformsiOSiPadOSAndroid

The back button of this screen: its title, showTitle, icon, color, visible, and on iOS its displayMode.

s.backButton({ showTitle: false });

On iOS the back button shows on the next screen, with this screen's title, so a title here is what the screens pushed over this one show. See The back button.

Buttons​

Bar buttons take onPress, which gets the navigation of their screen. The function stays in JavaScript; the native side gets the button's id. Every button needs an id.

leftButtons​

leftButtons(...leftButtons: NavigationButton[]): this
PlatformsiOSiPadOSAndroid

The buttons at the leading edge of the top bar, one argument each, in order.

s.leftButtons({
id: 'cancel',
systemItem: 'cancel',
onPress: ({ navigation }) => navigation.dismiss(),
});

leftButtons() with no argument takes the buttons away. See Bar buttons.

rightButtons​

rightButtons(...rightButtons: NavigationButton[]): this
PlatformsiOSiPadOSAndroid

The buttons at the trailing edge of the top bar. A button has a text, an icon (an image, or { ios, android } with an SF Symbol and a drawable), or a systemItem; enabled: false dims it; a menu opens on a tap, with an onPress for each item.

s.rightButtons({
id: 'share',
icon: { ios: 'square.and.arrow.up', android: 'ic_share' },
onPress: () => share(), // your own
});

A later rightButtons(…) replaces all of them, so a screen that changes one button gives the whole list again. See What a button can be.

centerButtons​

centerButtons(...centerButtons: NavigationButton[]): this
PlatformsiOSiPadOSAndroid (not supported)

iOS Buttons in the middle of the top bar, in place of the title, such as a segmented choice. Android ignores them. See Center buttons.

s.centerButtons(
{ id: 'all', text: 'All' },
{ id: 'unread', text: 'Unread' },
);

toolbar​

toolbar(toolbar: ToolbarOptions): this
PlatformsiOSiPadOSAndroid (not supported)

iOS A row of buttons at the bottom of the stack, while this screen is on top. Its buttons take onPress; { systemItem: 'flexibleSpace' } between them pushes the next ones to the far side.

s.toolbar({
buttons: [
{ id: 'filter', icon: { ios: 'line.3.horizontal.decrease' } },
{ systemItem: 'flexibleSpace' },
{ id: 'compose', icon: { ios: 'square.and.pencil' } },
],
});

Android ignores it; the FAB is its main action there. See The toolbar.

fab​

fab(fab: NavigationFab): this
PlatformsiOS (not supported)iPadOS (not supported)Android

Android The floating action button: the main action of the screen, at the bottom corner. It and each of its actions take onPress.

s.fab({
id: 'compose',
icon: { android: 'ic_edit' },
onPress: ({ navigation }) => navigation.showModal('Compose'),
});

iOS ignores it. One screen can set both a toolbar and a FAB, each for its platform. See The floating action button.

Search​

searchBar(searchBar: OptionsSearchBar): this
PlatformsiOSiPadOSAndroid

A search field in the top bar. The screen hears the text with useSearchBar.

s.title('Inbox').searchBar({
visible: true,
placeholder: 'Search mail',
});

See Search.

The screen and the system​

backgroundColor​

backgroundColor(backgroundColor: Color): this
PlatformsiOSiPadOSAndroid

The background of the screen, behind its React content. A color, or { light, dark }.

s.backgroundColor({ light: '#FFFFFF', dark: '#000000' });

Without it, the background of the theme. See Backgrounds.

safeArea​

safeArea(...edges: SafeAreaEdge[]): this
PlatformsiOSiPadOSAndroid

The edges on which the screen keeps its content out of the safe area: under the bars, the status bar, the home indicator, a cutout. One argument per edge: 'top', 'right', 'bottom', 'left'; none for content that goes edge to edge.

s.safeArea('top', 'left', 'right', 'bottom'); // a form
s.safeArea(); // a full-screen photo

Without it: the top and the sides, but those where the content goes under a bar (the top bar, the vertical bar at the side of iPhone Duo); the bottom is free, and a list keeps clear of it with useSafeAreaInsets().bottom. useSafeAreaInsets() gives what the screen doesn't pad. See The safe area.

orientation​

orientation(...orientation: LayoutOrientation[]): this
PlatformsiOSiPadOSAndroid

The orientations the screen allows, one argument each: 'portrait', 'landscape', 'all', and on Android the sensor values. The screen the user sees decides; when the current orientation isn't allowed, the screen rotates.

s.orientation('landscape'); // a video player

iOS On iPad it applies only to an app that requires full screen; an app with multitasking rotates with the device. See Orientation.

verticalBars​

verticalBars(verticalBars = true): this
PlatformsiOSiPadOSAndroid (not supported)

iOS 27.1 On iPhone Duo the system puts the buttons of the bars, the tab bar and the status bar in a vertical bar at the side. false keeps the bars of this screen and the status bar horizontal, as on any iPhone.

s.verticalBars(false); // the bars at the top, as on any iPhone

Set it once for a screen: Apple asks apps not to switch it with the state of the screen. See Buttons on iPhone Duo.

statusBar​

statusBar(statusBar: OptionsStatusBar): this
PlatformsiOSiPadOSAndroid

The status bar while this screen shows: style ('light' or 'dark' text), visible, and on Android its backgroundColor and drawBehind.

s.statusBar({ style: 'light' }); // over a dark photo

See The status bar.

navigationBar(navigationBar: NavigationBarOptions): this
PlatformsiOSiPadOSAndroid

Android The system navigation bar at the bottom while this screen shows: backgroundColor, visible, drawBehind.

s.navigationBar({ backgroundColor: '#000000' });

See The bottom of the system.

scrollEdgeEffect​

scrollEdgeEffect(scrollEdgeEffect: OptionsScrollEdgeEffect): this
PlatformsiOSiPadOSAndroid (not supported)

iOS iOS 26: the fade of the content under the bars, drawn by the scroll views of the screen. style ('automatic', 'soft', 'hard'), hidden, or per edge with top and bottom.

s.scrollEdgeEffect({ style: 'hard' });

See The edge of the scroll.

keyCommands​

keyCommands(...keyCommands: OptionsKeyCommand[]): this
PlatformsiOSiPadOSAndroid

iOS Keyboard shortcuts while this screen shows, one argument each. A press sends keyCommandPressed with the shortcut's id; the title is what iPad lists while the user holds ⌘.

s.keyCommands({
id: 'new',
input: 'n',
modifiers: ['command'],
title: 'New Message',
});

See Keyboard shortcuts.

layout​

layout(layout: OptionsLayout): this
PlatformsiOSiPadOSAndroid

Any option of the screen's layout the steps don't name, such as autoHideHomeIndicator on iOS or adjustResize on Android. safeArea, backgroundColor and orientation are steps of their own over it.

s.layout({ autoHideHomeIndicator: true }); // a video player

Every key: System UI options.

Going back​

popGesture​

popGesture(popGesture: boolean | 'edge'): this
PlatformsiOSiPadOSAndroid (not supported)

iOS The swipe back to the screen under this one. false turns it off; 'edge' keeps the swipe from the edge only, so the content's own horizontal gestures (a carousel, a slider) aren't taken over, as they can be by the full-width swipe of iOS 26.

s.popGesture('edge');

The back button still works; to keep the user on a screen, use usePreventLeave. See The swipe back.

hardwareBackButton​

hardwareBackButton(hardwareBackButton: HardwareBackButtonOptions): this
PlatformsiOSiPadOSAndroid

Android What the system back does on the first screen of a tab: bottomTabsOnPress is 'exit' (leave the app), 'first' (go to the first tab) or 'previous' (go to the tab before).

s.hardwareBackButton({ bottomTabsOnPress: 'first' });

For the whole tabs, Tabs().backBehavior(…) says the same once. See The system back of Android.

Transitions​

animations​

animations(animations: Pick<AnimationOptions, 'push' | 'pop'>): this
PlatformsiOSiPadOSAndroid

How this screen comes in when it is pushed (push) and goes when it is popped (pop), in detail. For the usual cases the transition of push is shorter. A command's transition goes over the screen's own animations, and the options.animations of the command go over the transition.

s.animations({ push: { enabled: false }, pop: { enabled: false } });

The screen shows and leaves without an animation. See Animations of your own.

The containers​

The options of the tabs and of the side menu are a screen's too: they apply while this screen is on top, and the container's own come back when the user leaves it.

hideBottomTabs​

hideBottomTabs(hidden = true): this
PlatformsiOSiPadOSAndroid

Hides the tab bar while this screen is on top; hideBottomTabs(false) shows it. A chat pushed from Inbox takes the whole height:

s.title('Chat').hideBottomTabs();

The tab bar comes back when the user goes back to Inbox. See Screens without the tab bar.

bottomTabs​

bottomTabs(bottomTabs: OptionsBottomTabs): this
PlatformsiOSiPadOSAndroid

Any option of the tab bar, while this screen is on top. Every key: Tabs options.

// iOS 26: the tab bar shrinks while this screen scrolls down
s.bottomTabs({ minimizeBehavior: 'onScrollDown' });

sideMenu​

sideMenu(sideMenu: OptionsSideMenu): this
PlatformsiOSiPadOSAndroid

The side menu while this screen is on top, such as no swipe to open it on a screen with its own horizontal gestures:

s.sideMenu({ left: { enabled: false } });

navigation.openMenu('left') still opens it. See Where the swipe is off.

Any option​

options​

options(options: Options): this
PlatformsiOSiPadOSAndroid

Any options, as the Options object: for those no step names, and for options that come as an object, such as from JSON. It merges over the steps before it, as every step does.

s.title('Profile').options({ blurOnUnmount: true });

Platforms​

ios​

ios(build: (options: this) => this): this
PlatformsiOSiPadOSAndroid

Steps for iOS only: the function gets the chain and returns it with its steps; on Android it isn't called.

s.title('Profile')
.ios((s) => s.largeTitle())
.android((s) => s.statusBar({ style: 'light' }));

Options of one platform need no ios(): the other platform ignores them. Use it for a value both platforms take but should differ.

android​

android(build: (options: this) => this): this
PlatformsiOSiPadOSAndroid

Steps for Android only; on iOS the function isn't called.

s.android((s) => s.backgroundColor('#FAFAFA'));

The chain returns a new value​

Each step returns a new chain and leaves the one it was called on as it was. A step whose result isn't returned is lost:

// Don't: the title is lost
options: (s) => {
s.title('Inbox');
return s;
},

Return the last step. To add a step only sometimes, keep the result:

options: (s, props) => {
const base = s.title('Inbox');
return props.editable
? base.rightButtons({ id: 'edit', text: 'Edit' })
: base;
},

Taking an option away​

Options merge, so leaving a step out of a later layer doesn't remove what an earlier one set. Set the empty value:

  • buttons: rightButtons() with no argument;
  • the top bar or the tab bar: hideTopBar(false), hideBottomTabs(false);
  • a title or a color: give the value you want.

This matters most with useScreenOptions: a render that stops calling .rightButtons(…) leaves the old buttons in place.

See also​

Generated from the types and their comments. Edit the source ↗