Skip to main content

Introduction

What @apexnative/navigation is, how it differs from a router in the React tree, and which apps it fits.

@apexnative/navigation gives a React Native app the navigation of the platform itself: the stacks, tabs, sheets and gestures of iOS and Android. You describe the screens and how they are arranged in TypeScript; the native side shows them and reports back.

Navigation.ts
import { createNavigation } from '@apexnative/navigation';
// your own components
import { Details, Home } from './src/screens';

export const Navigation = createNavigation({
screens: { Home, Details },
root: 'Home',
});
iOS
Android

Home opens in a native stack. When Home calls navigation.push('Details'), Details slides in with the system transition, and the swipe back iOS or the back button Android returns to Home. The Quick start builds this app step by step.

info · Before 1.0

The package is a pre-release: the banner at the top of each page says which one and how to install it, and the API can still change before 1.0.

Three ways to build navigation​

Navigation for React Native is built in one of three ways. They answer two questions differently: who owns the navigation state, and how your code speaks to it.

All three can show native screens. The difference is not in how a screen is drawn, but in where the state lives, who decides what the user sees, and what your code has to say to change it.

Model AA router in the React tree
Model BNative navigation by commands
Model C · this libraryNative navigation, described in React
State
A router in the React treeAn object in the React tree. Native containers, when the router uses them, follow it.
Native navigation by commandsThe stack, the tabs and the modals of the OS.
Native navigation, described in ReactThe stack, the tabs and the modals of the OS. JavaScript reads them through events and hooks.
A screen
A router in the React treeA component in one shared React tree.
Native navigation by commandsA component registered by name, with a React root of its own.
Native navigation, described in ReactA component defined next to its options: a root of its own, or one shared tree when the app wants it.
Navigation is
A router in the React treeNavigators as components, or files and folders where every screen is a URL.
Native navigation by commandsLayouts as JSON objects; every command takes the id of a screen.
Native navigation, described in ReactBuilders: Stack, Tabs, SideMenu. A screen sends commands without ids.
Types
A router in the React treeTyped route params.
Native navigation by commandsScreen names are strings; props are not checked.
Native navigation, described in ReactScreen names, props and roots are checked by TypeScript.
Gestures
A router in the React treeNative or drawn in JS; either way, synced back into the React state.
Native navigation by commandsThe system's own.
Native navigation, described in ReactThe system's own: swipe back, the back button, dragging a sheet.
App logic
A router in the React treeLives in providers of the render tree.
Native navigation by commandsListeners on one global navigation object.
Native navigation, described in ReactOutside rendering: willMount, the app's context as services, navigation outside React.
Native screens
A router in the React treeA native view inside a React screen.
Native navigation by commandsRegistered by hand on the native side.
Native navigation, described in ReactSwiftUI, UIKit and Compose screens next to React screens, set up by the CLI.
The idea in one line

The platform owns the navigation. React describes it.

Describe, don't command​

When the platform owns the navigation, the app still has to speak to it. Commands with string ids and JSON layouts work, but every mistake in them shows up at run time: a wrong name, a missing prop, an id of a screen that is gone. The library keeps the native model and gives it a React API, so the same mistakes show up in the editor.

Commands by id
navigator.push(screenId, {
name: 'Details',
props: { id: 42 },
});
A typed description
const navigation = useNavigation();
navigation.push('Details', { props: { id: '42' } });
// 'Detail' or a missing id: a TypeScript error

The platform already knows how to navigate​

iOS and iPadOS have UINavigationController, UITabBarController, sheets with detents and UISplitViewController. Android has its back stack, the back button and the Material components. Each of them holds years of decisions about gestures, accessibility, layout on every screen size, memory and multitasking.

A router that keeps its own model of the navigation has to mirror all of that into its state, and keep up with every release of the OS. The platform already does it. So the library gives React access to these containers instead of replacing them, and a new tab bar or toolbar of the OS arrives with its beta, not a year later.

note

The native look is a consequence, not the goal. The goal is native behavior: the same gestures, transitions, accessibility and memory use as in an app written in Swift or Kotlin.

Principles​

Seven rules that every part of the API follows. When two of them pull apart, the order decides.

01

The platform leads

Native stacks, tabs, sheets and gestures, not a web-style router. A URL is one entry point among push notifications, widgets and shortcuts.
02

Native is the source of truth

Navigation state, screen lifecycle, safe areas, keyboard and theme come from the OS.
03

App lifecycle is first-class

Cold start, splash, session restore, background and process death are explicit steps.
04

New OS features, fast

Support for new iOS and Android releases lands with the betas.
05

Customization without losing native behavior

Options of system components first, your own native components inside native containers next, custom JS UI last.
06

Performance is a requirement

Transitions and gestures run natively. JavaScript stays off the critical path.
07

Rendering and logic apart

React renders screens. The logic of the app (a socket, a session, services) lives outside the render tree: navigation works outside React, willMount prepares the app, and the app's context is a set of services, not a store.

How it works​

The library has three layers. Your app describes the screens and the layout. A thin layer in JavaScript, the engine, turns that into commands. The platform does the navigation and sends events back.

The life of one push, from the tap to the next render:

  1. 1A screen of your app asks for the next one.navigation.push('Details')
  2. 2The engine turns it into a layout and sends it to the native side.
  3. 3The platform creates the container and the React root of the screen.
  4. 4The transition runs natively, with the system's gestures.
  5. 5Events come back, and hooks render again.screenDidAppear · stateChanged

What it means for your code​

Four habits follow from the model. Each of them comes from the same fact: the platform, not React, holds the screens.

  • Read the state, don't keep a copy. The platform owns the stack, so a copy in React state goes stale after the first swipe back. Read it with useNavigationState, and listen to events instead of guessing.
  • One React root per screen, by default. Each screen renders on its own, so a provider around one screen doesn't reach the others. Providers for the whole app or for every screen go to createNavigation; with appProvider, all screens share one React tree. More: One tree.
  • No safe area code. Every screen keeps its top and sides clear of the status bar, the cutout and the bars by itself (the bottom is free: a list adds useSafeAreaInsets().bottom), without SafeAreaView or a safe area library. The native side measures each screen, so the insets are right under a top bar, in a sheet and in a tab. More: The safe area.
  • Native screens are screens like the others. A SwiftUI, UIKit or Compose screen is pushed, presented and put in tabs the same way as a React screen. More: Native screens.

When it fits​

Fits
  • Apps that should look and behave like the platform, on iPhone, iPad and Android.
  • Apps with native screens, or a team that also writes Swift or Kotlin.
  • Apps where startup, transitions and memory matter.
Not a fit
  • A web version of the same app: the web is not a target.
  • One identical, fully custom UI on every platform, drawn in JavaScript.

How these docs are built​

Each section of the sidebar is one part of the API, and goes the same way:

  1. Writtenthe page and its API reference
  2. API reviewednames, types, defaults
  3. CheckediOS, iPadOS and Android, in the Showcase
  4. Stablethe API stays as documented

The dot next to a section shows where it is.

DotStatus
Stable: checked on every platform
In review: can still change
Draft

Where to go next​