Navigation
Use the adapter for your router. Navigation hooks require a mounted provider and the corresponding route context.
useChoreographyNavigation
Available from react-native-screen-choreography.
const { navigate, goBack } = useChoreographyNavigation(navigation);
navigate(
screenName: string,
params?: any,
options?: ChoreographyNavigateOptions,
): Promise<void>;
goBack(options?: ChoreographyNavigationOptions): Promise<void>;Pass the current screen's React Navigation navigation object. navigate uses navigation.navigate to open the destination and coordinates the shared transition.
The returned object and its commands keep the same identities while the hook remains mounted. Retained command references use the latest committed navigation and screen binding, so they can be passed through context or used in event handlers without a ref wrapper.
Call commands from event handlers or layout/passive effects, never during render. Once a request starts, it keeps its original navigation action and source binding through asynchronous preparation. Stop using commands when their hook unmounts; retaining a command does not keep its screen mounted or focused.
void navigate(
'Detail',
{ artworkId: '42' },
{
transitionConfig: { group: 'artwork.42' },
spring: Springs.default,
}
);Without transitionConfig.group, navigation is ordinary navigation. For compatibility, the root adapter also reads params.transitionGroup when explicit transitionConfig is absent. Prefer the explicit option so route data and motion stay separate.
The returned promise is not an animation-completion signal. Navigation preparation is asynchronous and the animation runs independently. Use onNavigationEvent for this request's queue and terminal outcome, or provider lifecycle callbacks for session events.
Navigation options
interface ChoreographyNavigationOptions {
transitionConfig?: { group: string };
spring?: SpringConfig;
duration?: number;
}
interface ChoreographyNavigateOptions extends ChoreographyNavigationOptions {
onNavigationEvent?: (event: ChoreographyNavigationEvent) => void;
}Forward navigation defaults to Springs.default. A positive duration selects a timing animation in milliseconds with cubic ease-out. Keep timing values finite and positive.
Back returns shared content to the screen it came from and reuses the forward spring. A forward timing duration is not reused on return.
See quick start for the required stack setup.
useChoreographyRouter
Available from react-native-screen-choreography/expo-router.
const { push, navigate, back } = useChoreographyRouter(router, currentScreenId);
push(request: ChoreographyRouterRequest<Href>): Promise<void>;
navigate(request: ChoreographyRouterRequest<Href>): Promise<void>;
back(options?: ChoreographyNavigationOptions): Promise<void>;
type ChoreographyRouterRequest<Href> = ChoreographyNavigateOptions & {
href: Href;
targetScreenId: string;
};router must provide push, navigate, and back. ExpoRouterLike<Href> and ChoreographyRouterRequest<Href> are exported from the Expo entry.
const { push } = useChoreographyRouter(useRouter(), 'Collection');
void push({
href: { pathname: '/artwork/[id]', params: { id: '42' } },
targetScreenId: 'ArtworkDetail',
transitionConfig: { group: 'artwork.42' },
});Match currentScreenId to the current wrapper and targetScreenId to the destination wrapper. push adds a route and navigate uses the router's navigation behavior; both coordinate shared motion. URLs only need your application's route parameters.
The returned object and its push, navigate, and back commands keep the same identities while the hook remains mounted. Retained command references use the latest committed router and screen binding.
The same command lifetime, timing, and promise caveats as the React Navigation adapter apply. See Expo Router.
ChoreographyNavigationEvent
Pass onNavigationEvent to navigate or Expo Router's push to observe one forward request through queueing, preparation, and settlement. The callback receives a stable requestId, a sourceScreenId, a targetScreenId, and a nullable sessionId. The target starts as the requested screen name and becomes the resolved route-instance ID when available.
| Status | finished | Meaning |
|---|---|---|
queued | false | Waiting for navigation ownership or source focus. |
started | false | The request began preparation, or ordinary navigation was dispatched. This does not prove that a frame is visible. |
completed | true | The choreography settled at this request's destination after presentation, through animation completion or explicit settlement. |
superseded | true | A newer request replaced this queued request. reason is newer-request. |
cancelled | true | The request cannot continue, or its session settled away from the destination. |
fallback | true | The request finished without a fully presented animated choreography. Ordinary navigation or direct reduced-motion transfer owns the result. |
failed | true | Preparation or dispatch threw; error contains the original value. The navigation promise still rejects when the error propagates. |
Each observed request emits exactly one terminal event (finished: true) once it ends. A queued request can remain pending while its source stays mounted but unfocused. Replay keeps the same ID and observer. Back during an unfinished opening can cancel that opening; these observers do not create a separate Back request. back and goBack continue to accept ChoreographyNavigationOptions.
Cancellation reasons are source-removed, provider-unmounted, interrupted, target-unavailable, blocked, and queue-cleared. Fallback reasons are no-transition, reduced-motion, screen-not-ready, transition-unavailable, and overlay-unavailable. A fallback event reports the choreography outcome, not proof that native presentation or input handoff completed. In particular, no-transition follows the ordinary navigation dispatch.
const launchToken = useRef(0);
function openArtwork(id: string) {
const token = ++launchToken.current;
setLaunching(true);
void push({
href: { pathname: '/artwork/[id]', params: { id } },
targetScreenId: 'ArtworkDetail',
transitionConfig: { group: `artwork.${id}` },
onNavigationEvent(event) {
if (!event.finished || launchToken.current !== token) return;
setLaunching(false);
if (event.status === 'fallback') showSettledContent();
},
}).catch(reportError);
}
useEffect(
() => () => {
launchToken.current += 1;
},
[]
);The controller retains the observer across queue replay and session settlement, independently of the calling component. Guard component-local updates after unmount, as above. Callbacks run on JavaScript after the current bookkeeping completes; exceptions in an observer are caught and logged through the library's debug logger so they cannot strand navigation. No observer is retained when the option is omitted. Existing navigation promises retain their preparation/queueing semantics; awaiting them does not wait for the terminal event.
useInteractiveGestureLifecycle
Available from /core and both integration entries. Connects a custom UI-thread gesture to asynchronous interactive back preparation. Pass a controller from useInteractiveTransition() in the route that should be dismissed; a child can receive that controller through props to preserve the parent navigator binding.
const { begin, update, release } = useInteractiveGestureLifecycle(
controller,
options
);
begin(): number;
update(ticket: number, progress: number): void;
release(ticket: number, result?: InteractiveGestureRelease): void;
interface InteractiveGestureController {
beginBack(options?: InteractiveBackOptions):
Promise<InteractiveTransitionHandle | null>;
}
interface InteractiveGestureOptions {
group?: string;
targetScreenId?: string;
scopeKey?: unknown;
enabled?: boolean; // true
animate?: boolean; // true
spring?: SpringConfig;
duration?: number;
threshold?: number; // 0.5
velocityImpact?: number; // 0.2 seconds
onFallbackFinish?: () => void;
}
interface InteractiveGestureRelease {
progress?: number; // Defaults to the last update.
velocity?: number; // Normalized progress units per second; defaults to 0.
cancelled?: boolean; // false
}All three callbacks are worklets, so you can call them directly from gesture handlers. begin() returns an attempt ticket, or 0 when disabled or busy. Pass that ticket to every update and release to keep them tied to the same gesture. Updates are clamped to [0, 1]. Movement and release are buffered while the transition prepares, so a quick drag still works before the overlay is ready. Obsolete tickets and duplicate releases are ignored.
Release uses the same projected-progress decision as settle below. cancelled: true always cancels. If no session is available, onFallbackFinish runs on JavaScript only when the release qualifies to finish. animate: false skips preparation and uses that fallback path, so applications can respect reduced motion without disabling dismissal.
Changing group, targetScreenId, scopeKey, enabled, or animate, or unmounting, abandons the current attempt. Set scopeKey when the application's interaction identity changes, such as the selected gallery item. Gesture Handler remains an optional application dependency. See the complete pan example.
useInteractiveTransition
Available from both integration entries. Uses the current route and provider to prepare and control a custom reverse transition.
const {
beginBack, setProgress, finish, cancel, settle, progress, isActive,
} = useInteractiveTransition();
beginBack(options?: InteractiveBackOptions):
Promise<InteractiveTransitionHandle | null>;
setProgress(value: number): void; // Worklet; clamped to [0, 1].
finish(options?: InteractiveTransitionSettleOptions): void;
cancel(options?: InteractiveTransitionSettleOptions): void;
settle(options?: InteractiveTransitionDecisionOptions): void;| Return value | Meaning |
|---|---|
progress | SharedValue<number> of gesture progress: 0 is untouched detail; 1 is completed back |
isActive | React boolean; true after preparation, false when a reverse commit begins, cancellation completes, or the session is removed |
beginBack | Prepares the overlay; returns a session-bound handle, or null if no session was acquired |
setProgress | Updates a currently owned interactive session; stale or inactive calls are ignored |
finish | Settles toward completing the back navigation |
cancel | Settles back to the expanded detail |
settle | Chooses finish or cancel from projected progress |
interface InteractiveBackOptions {
group?: string; // Defaults to recorded forward lineage.
targetScreenId?: string; // Defaults to the recorded source screen.
signal?: AbortSignal; // Cancels pending preparation only.
}
interface InteractiveTransitionSettleOptions {
spring?: SpringConfig;
duration?: number;
velocity?: number; // Normalized progress units per second.
}
interface InteractiveTransitionDecisionOptions extends InteractiveTransitionSettleOptions {
threshold?: number; // 0.5
velocityImpact?: number; // 0.2 seconds
}Settlement defaults to a fast spring. settle finishes when clamped progress + velocity × velocityImpact reaches threshold; velocity defaults to zero. Explicit duration chooses timing instead of a spring.
InteractiveTransitionHandle
beginBack() returns an InteractiveTransitionHandle with the session's id and progress, plus callbacks bound to that transition:
interface InteractiveTransitionHandle extends InteractiveTransitionSession {
// Inherited: id: string; progress: SharedValue<number>.
setProgress(value: number): void; // Worklet.
finish(options?: InteractiveTransitionSettleOptions): void; // JavaScript.
cancel(options?: InteractiveTransitionSettleOptions): void; // JavaScript.
}When the destination is still opening, beginBack() takes ownership of that session and preserves its current progress and overlay. Initialize any separate animation driver from handle.progress.value; an interrupted opening can return a value above 0. Cancelling restores the destination, while finishing returns to the original source.
The handle can be used immediately after await beginBack() without waiting for a React render. Its callbacks ignore stale ownership and cannot control a later session. Aborting signal cancels preparation before a handle is returned; after readiness, use handle.cancel().
Lifecycle methods run on JavaScript
Only setProgress is a worklet on this lower-level API. Call beginBack, finish, cancel, and settle on JavaScript, or use scheduleOnRN from a worklet. Use the returned handle for session-bound callbacks, or fresh hook callbacks from the render where isActive is true. Prefer useInteractiveGestureLifecycle for custom gesture bindings.