Lynx/Modules/Navigation/API reference
@sigx/lynx-navigation · Stable · Component library

API reference#

Every export of @sigx/lynx-navigation — functions, hooks, components and types.

The package is ESM-only with a single entry point (.). All inference flows from augmenting the Register interface with your route map; see Usage for the setup pattern.

Functions#

defineRoutes#

TypeScript
function defineRoutes<const T extends RouteMap>(routes: T): T

Defines a typed route registry. Returns its input verbatim — it exists purely for TypeScript inference, narrowing to a literal route map so useNav, useParams and <Link> can infer route names and per-route param/search schemas.

hrefFor#

TypeScript
function hrefFor<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>): Href<K>;
function hrefFor<K extends RoutesWithParams>(name: K, params: RouteParams<K>, search?: RouteSearch<K>): Href<K>;

Builds a typed Href for a route, validating params and search against the route's schema at runtime (throws on validation failure). Overloaded by whether the route declares a params schema. Requires an active route registry (a mounted NavigationRoot, or _setRouteRegistry in tests). The returned url is null when the route has no path template.

parseHref#

TypeScript
function parseHref(url: string): Href | null

Parses a URL string (absolute scheme://… or pathname-only) into a typed Href against the registered routes' path templates. Returns null when no path matches or params/search fail validation. First registration-order match wins. Takes only (url) at runtime.

compilePath#

TypeScript
function compilePath(template: string): CompiledPath

Compiles a path template (e.g. '/users/:id') into a CompiledPath with a regex and a format() method. Supports literal segments, :name params (matching [^/]+) and tolerated trailing slashes. Throws on an empty / non-string template or duplicate param names. Pure and memoizable. No wildcards, optional or typed params in v1.

_setRouteRegistry#

TypeScript
function _setRouteRegistry(routes: RouteMap): void

Test / integration use, not part of the supported public API. Imperatively seeds the module-level URL bridge registry (for tests or deep-link bootstrap before a NavigationRoot mounts). Last write wins; NavigationRoot calls this on setup.

_clearRouteRegistry#

TypeScript
function _clearRouteRegistry(): void

Test use, not part of the supported public API. Clears the module-level URL bridge route registry.

Hooks#

useNav#

TypeScript
const useNav: () => Nav

Accesses the innermost navigator handle, provided by NavigationRoot. Returns a Nav with reactive read getters (current, stack, canGoBack) and mutators (push, replace, pop, popTo, popToRoot, reset, dismiss). Throws when no NavigationRoot is mounted.

useParams#

TypeScript
function useParams<K extends RouteId>(_name: K): RouteParams<K>

Reads typed params for the current screen, asserted against the named route. The name argument is a compile-time discriminator — a wrong route name is a TS error. Snapshot at setup (screens remount on navigation).

useSearch#

TypeScript
function useSearch<K extends RouteId>(_name: K): RouteSearch<K>

Reads typed search / query params for the current screen, asserted against the named route. The name argument is a compile-time discriminator. Snapshot at setup.

useHardwareBack#

TypeScript
function useHardwareBack(): void

Wires the Android hardware / system back button to the active navigator. Idempotent per nav tree (first registration wins). No-op on iOS and in non-native runtimes. NavigationRoot auto-wires this by default — call it only when you set hardwareBack={false}. Platform: Android.

useLinkingNav#

TypeScript
function useLinkingNav(opts: UseLinkingNavOptions = {}): void

Bridges @sigx/lynx-linking URL events (cold-start getInitialURL plus warm-start 'url' events) into the navigator via parseHref. Call once inside a NavigationRoot subtree. Requires the optional @sigx/lynx-linking peer.

useIsFocused#

TypeScript
function useIsFocused(): Computed<boolean>

Returns a reactive Computed<boolean> (read .value) — true while this screen is the visible top of its navigator and every ancestor (parent entry plus enclosing tab) is focused. Must be called inside a route component rendered by Stack; throws otherwise.

useFocusEffect#

TypeScript
function useFocusEffect(cb: () => void | (() => void)): void

Runs cb when the screen gains focus and runs its returned cleanup on blur or unmount. Re-runs on each focus return. Use for analytics, subscriptions or video playback while visible.

useDidAppear#

TypeScript
function useDidAppear(cb: () => void | (() => void)): void

The heavy-work sibling of useFocusEffect. Runs cb once the screen is focused and no transition is in flight — not before, and not on every focus regardless of motion; the returned cleanup runs on blur, on a new transition, or on unmount.

useFocusEffect cannot serve this case. useIsFocused is nav.current.key === myKey, and a push commits the stack immediately — so a focus effect fires while the slide is still running.

That matters more than it sounds. Component setup runs on the background thread, but the resulting op batch is applied on the main thread in one uninterruptible pass — the same thread driving the tween. Mount work scheduled on focus therefore starves the animation it is running under.

TSX
useFocusEffect(() => { analytics.screen('Chat'); });     // cheap — fine on focus
useDidAppear(() => { const s = subscribeToFeed(); return () => s.close(); });

Reach for useDidAppear when the work is expensive enough to be felt: opening a socket, decoding media, hydrating a long list.

useScreenOptions#

TypeScript
function useScreenOptions(optionsOrFn: ScreenOptions | (() => ScreenOptions)): void

Imperatively merges ScreenOptions (title, header / gesture flags, sheet options) into the current entry. A plain object merges once; a function is a tracked effect that re-merges on signal change. Strictly additive (per-key writes).

useScreenChrome#

TypeScript
function useScreenChrome(): ScreenChrome

Reactive read of the focused screen's options and slot fills, plus header helpers (title, headerShown, canGoBack, pop, header, headerLeft, headerRight). The public foundation for custom header components; every property is a getter. Resolves to the scoped entry inside an EntryScope, otherwise to the navigator's destination entry.

useNavSerializer#

TypeScript
function useNavSerializer(options: UseNavSerializerOptions): void

Persists the navigator's root stack across launches via a NavStorageAdapter. On mount it loads and validates (version / shape / unknown-route) then applies with nav.reset(); it subscribes to nav.stack and saves debounced (default 250 ms). Only the root navigator is persisted in v1 (nested / per-tab stacks are not yet).

useTabs#

TypeScript
const useTabs: () => TabsNav

Accesses the enclosing Tabs navigator: { active, setActive(name), tabs }. Reactive. Throws when called outside Tabs. setActive ignores unknown tab names (no-op).

useDrawer#

TypeScript
const useDrawer: () => DrawerNav

Accesses the enclosing Drawer navigator: { isOpen, open(), close(), toggle() }. Reactive. Throws when called outside Drawer.

useSheetHeight#

TypeScript
const useSheetHeight: () => SharedValue<number>

The live visible height in px of the top bottom sheet — 0 when none is open, rising to its detent height as the sheet opens or drags, tracking the finger frame for frame. Reveal px is the visible height, so there is no progress→px scaling and no per-sheet rebind: the identity is stable for consumers.

Bind it like any SharedValue. The canonical use is a composer bar that must sit above whichever is taller, the soft keyboard or the sheet:

TSX
const sheetH = useSheetHeight();
const lift = useDerivedValue([keyboardLift, sheetH], 'max');
useAnimatedStyle(barRef, lift, 'translateY', { factor: -1 });

Two separate translateY bindings would sum — see useDerivedValue.

Must be called under a <NavigationRoot>. Returns a constant-0 SharedValue when the navigator was created with animated={false}, since there is no sheet SV to track.

Components#

Each component has its own page with usage, props, slots and events — see the Components catalog:

  • Stack — the stack navigator; renders the enclosing stack in bound mode or mints a fresh nested navigator (per-tab back-stacks) in nested-owner mode.
  • Tabs — a persistent tab navigator hosting <Tabs.Screen>; tab bodies stay mounted to preserve per-tab state, and it provides useTabs().
  • Drawer — a minimal off-canvas drawer navigator with a sidebar and default slot; provides useDrawer().
  • NavigationRoot — the root of a navigator subtree; creates and provides a fresh navigator state from routes (isolated per root).
  • Screen — declarative per-screen options and slot fills, plus its Screen.Header / Screen.HeaderLeft / Screen.HeaderRight / Screen.TabBarItem sub-components.
  • Header — the headless default navigator header chrome (back button, title, header slots), switching content reactively.
  • TabBar — the headless default chrome for Tabs; unstyled buttons with accessibility wiring, overridable via renderTab.
  • Link — declarative navigation, the JSX flavor of nav.push, with the same per-route conditional typing.

Their prop types and related APIs remain documented below.

Constants#

TypeScript
const NAV_SNAPSHOT_VERSION = 1

The current nav snapshot schema version (value 1). Stored in NavSnapshot.version; bump it to reject old snapshots (onRestoreError fires 'version').

Types#

TypeScript
interface Nav {
    push<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>, options?: PushOptions): void;
    push<K extends RoutesWithParams>(name: K, params: RouteParams<K>, search?: RouteSearch<K>, options?: PushOptions): void;
    replace<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>, options?: PushOptions): void;
    replace<K extends RoutesWithParams>(name: K, params: RouteParams<K>, search?: RouteSearch<K>, options?: PushOptions): void;
    pop(count?: number, options?: PopOptions): void;
    popTo<K extends RouteId>(name: K): void;
    popToRoot(): void;
    reset(state: { stack: ReadonlyArray<StackEntry> }): void;
    dismiss(): void;
    readonly current: StackEntry;
    readonly stack: ReadonlyArray<StackEntry>;
    readonly canGoBack: boolean;
    readonly parent: Nav | null;
    readonly isLocallyFocused: boolean;
    readonly transition: TransitionState | null;
}

The navigator handle returned by useNav. Read getters are reactive; mutators commit immediately. push / replace are overloaded per route by whether params are required.

Register#

TypeScript
interface Register {} // empty — users augment it

Augment this via declare module '@sigx/lynx-navigation' with routes: typeof routes to register your route map. This drives all global type inference.

RegisteredRoutes#

TypeScript
type RegisteredRoutes = Register extends { routes: infer R } ? R : RouteMap;

Your registered route map, or the permissive RouteMap fallback when Register is not augmented.

RouteId#

TypeScript
type RouteId = keyof RegisteredRoutes & string;

The union of registered route names.

RouteParams#

TypeScript
type RouteParams<K extends RouteId> = ParamsOf<RegisteredRoutes[K]>;

The inferred params type for route K.

RouteSearch#

TypeScript
type RouteSearch<K extends RouteId> = SearchOf<RegisteredRoutes[K]>;

The inferred search type for route K.

RoutesWithParams#

TypeScript
type RoutesWithParams = { [K in RouteId]: RouteRequiresParams<RegisteredRoutes[K]> extends true ? K : never }[RouteId];

The union of route names that declare a params schema.

RoutesWithoutParams#

TypeScript
type RoutesWithoutParams = Exclude<RouteId, RoutesWithParams>;

The union of route names with no params schema.

RouteDefinition#

TypeScript
interface RouteDefinition<
    Params extends StandardSchemaV1 | undefined = StandardSchemaV1 | undefined,
    Search extends StandardSchemaV1 | undefined = StandardSchemaV1 | undefined,
> {
    component: ComponentLike;
    fallback?: ComponentLike | (() => unknown);
    params?: Params;
    search?: Search;
    path?: string;
    presentation?: Presentation;
    children?: Record<string, RouteDefinition>;
}

A single route entry. params / search are Standard Schema validators; path enables deep links; presentation defaults to 'card'.

RouteMap#

TypeScript
type RouteMap = Record<string, RouteDefinition>;

A map of route names to definitions.

ComponentLike#

TypeScript
type ComponentLike = ((...args: any[]) => unknown) | (() => Promise<{ default: (...args: any[]) => unknown }>);

A route component — a function component or a dynamic import (for lazy routes).

Presentation#

TypeScript
type Presentation = 'card' | 'modal' | 'fullScreen' | 'transparent-modal' | 'sheet';

How a screen is presented. card stays in its stack; the others escalate to root from nested stacks. sheet is a partial-height bottom sheet with a dimmed backdrop.

StackEntry#

TypeScript
interface StackEntry<R extends string = string, P = unknown, S = unknown> {
    readonly key: string;
    readonly route: R;
    readonly params: P;
    readonly search: S;
    state: unknown;          // user state, survives suspend/restore
    readonly presentation: Presentation;
}

One entry in a navigator stack.

PushOptions#

TypeScript
interface PushOptions { presentation?: Presentation; state?: unknown; animated?: boolean; }

Per-push overrides (override the route's presentation, seed entry state, or disable animation for one push).

PopOptions#

TypeScript
interface PopOptions { animated?: boolean; }

Per-pop options.

ScreenOptions#

TypeScript
interface ScreenOptions {
    title?: string | (() => string);
    headerShown?: boolean;          // default true
    gestureEnabled?: boolean;       // default true
    detents?: DetentSpec[];         // sheet: rest heights (fraction / px / keyboard); default [{ fraction: 0.5 }]
    initialDetentIndex?: number;    // default: last (most open)
    dragMode?: 'handle' | 'surface' | 'grabber';  // sheet: what owns the drag; renamed from dragHandle
    backdropDismiss?: boolean;      // default true
}

Per-screen options, settable via <Screen> or useScreenOptions. The detents / initialDetentIndex / backdropDismiss fields apply to sheet presentations. They use the DetentSpec geometry from @sigx/lynx-sheet — the route sheet now shares that engine, and the detents are tracked live. Renamed in 0.19 from snapPoints / initialSnapIndex (which took plain fraction numbers).

ScreenSlotFills#

TypeScript
interface ScreenSlotFills {
    header?: () => unknown;
    headerLeft?: () => unknown;
    headerRight?: () => unknown;
    tabBarItem?: (ctx: { active: boolean }) => unknown;
}

The slot fills a screen can register (written by the Screen.* sub-components).

ScreenChrome#

TypeScript
interface ScreenChrome {
    readonly title: string;
    readonly headerShown: boolean;
    readonly canGoBack: boolean;
    pop(): void;
    readonly header: ScreenSlotFills['header'] | undefined;
    readonly headerLeft: ScreenSlotFills['headerLeft'] | undefined;
    readonly headerRight: ScreenSlotFills['headerRight'] | undefined;
}

The reactive header data returned by useScreenChrome. Every property is a getter.

Href#

TypeScript
interface Href<K extends RouteId = RouteId> {
    readonly route: K;
    readonly params: RouteParams<K>;
    readonly search: RouteSearch<K>;
    readonly url: string | null;   // null when the route has no path
}

A typed, validated navigation target produced by hrefFor / parseHref.

CompiledPath#

TypeScript
interface CompiledPath {
    readonly source: string;
    readonly paramNames: readonly string[];
    readonly regex: RegExp;
    format(params: Record<string, string | number>): string;
}

The result of compilePath — a regex matcher plus a format() that fills the template.

TypeScript
interface NavSnapshot { version: number; stack: StackEntry[]; }

A persisted navigator snapshot (version plus stack).

TypeScript
interface NavStorageAdapter {
    load(): Promise<NavSnapshot | null> | NavSnapshot | null;
    save(snapshot: NavSnapshot): Promise<void> | void;
}

The storage backend for useNavSerializer. Implement load / save over any store (e.g. @sigx/lynx-storage).

UseNavSerializerOptions#

TypeScript
interface UseNavSerializerOptions {
    storage: NavStorageAdapter;
    debounceMs?: number;            // default 250
    onRestored?: (snapshot: NavSnapshot) => void;
    onRestoreError?: (reason: 'version' | 'shape' | 'unknown-route' | 'load-threw', err?: unknown) => void;
}

Options for useNavSerializer.

UseLinkingNavOptions#

TypeScript
interface UseLinkingNavOptions {
    prefixes?: string[];
    onURL?: (url: string, nav: Nav) => void;
    onUnmatched?: (url: string) => void;
    replaceInitial?: boolean;       // default true
}

Options for useLinkingNav.

TabInfo#

TypeScript
interface TabInfo {
    readonly name: string;
    readonly icon?: IconSpec | JSXElement;
    readonly label?: string;
    readonly accessibilityLabel?: string;
}

Metadata for a single tab (from each Tabs.Screen).

TabsNav#

TypeScript
interface TabsNav {
    readonly active: string;
    setActive(name: string): void;
    readonly tabs: ReadonlyArray<TabInfo>;
}

The handle returned by useTabs.

TabRenderContext#

TypeScript
interface TabRenderContext { readonly active: boolean; onPress(): void; }

The render context passed to TabBar's renderTab for each item.

DrawerNav#

TypeScript
interface DrawerNav { readonly isOpen: boolean; open(): void; close(): void; toggle(): void; }

The handle returned by useDrawer.

LinkProps#

TypeScript
type LinkProps = LinkPropsByRoute & { replace?: boolean; children?: unknown };
// LinkPropsByRoute = {
//   [K in RouteId]: K extends RoutesWithParams
//     ? { to: K; params: RouteParams<K>; search?: RouteSearch<K> }
//     : { to: K; params?: undefined; search?: RouteSearch<K> }
// }[RouteId]

The props of <Link>params is required exactly when the route declares a params schema.

TransitionState#

TypeScript
interface TransitionState {
    readonly kind: TransitionKind;
    readonly topEntry: StackEntry;
    readonly underneathEntry: StackEntry;
    readonly progress: unknown;   // SharedValue<number>, typed loosely
}

The in-flight transition exposed by nav.transition (null when idle).

TransitionKind#

TypeScript
type TransitionKind = 'push' | 'pop';

The direction of a transition.

TransitionRole#

TypeScript
type TransitionRole = 'top' | 'underneath';

A screen's role during a transition.

StandardSchemaV1#

TypeScript
interface StandardSchemaV1<Input = unknown, Output = Input> {
    readonly '~standard': {
        readonly version: 1;
        readonly vendor: string;
        readonly types?: { readonly input: Input; readonly output: Output };
    };
}

The Standard Schema interface used for params / search validators (zod, valibot, arktype all implement it).

InferOutput#

TypeScript
type InferOutput<S> = S extends StandardSchemaV1<unknown, infer O> ? O : unknown;

Extracts the output type of a Standard Schema.

EmptyParams#

TypeScript
type EmptyParams = Record<string, never>;

The params / search type for routes that declare no schema.

ParamsOf#

TypeScript
type ParamsOf<R> = R extends { params: infer S } ? InferOutput<S> : EmptyParams;

Infers a route definition's params type.

SearchOf#

TypeScript
type SearchOf<R> = R extends { search: infer S } ? InferOutput<S> : EmptyParams;

Infers a route definition's search type.

RouteRequiresParams#

TypeScript
type RouteRequiresParams<R> = R extends { params: object } ? true : false;

Whether a route definition declares a params schema.

See also#

  • Usage — practical guides and complete examples.
  • Overview — what Navigation is.