Navigation transitions: default to the platform
Every Frame.navigate() (and RouterExtensions.navigate() in Angular) accepts a transition option. The correct value, almost always, is none at all. Omit it and NativeScript hands the navigation to the OS — UINavigationController's push/pop on iOS, the native fragment transition (with predictive back) on Android.
// WRONG: downgrades the experience — looks close to native, feels wrong
frame.navigate({
moduleName: 'views/item/item-page',
context: { id: item.id },
transition: { name: 'slideLeft' },
});
// RIGHT: platform-default animation
frame.navigate({
moduleName: 'views/item/item-page',
context: { id: item.id },
});
Why the default wins
A custom transition (slideLeft, fade, flip, …) replaces the system's transition coordinator with a hand-rolled animator. What you lose:
- Coordinated chrome animation: the system transition morphs the whole navigation context together — ActionBar/nav-bar crossfade, back-button label slide, Liquid Glass morphing on iOS 26, large-title collapse. A custom animator moves only the page; the chrome snaps.
- Interactive gestures: iOS edge-swipe pop and Android predictive back are interactive, progress-driven versions of the default transition. Custom transitions at best replay a canned animation, at worst fight the gesture.
- Platform motion language: each OS's curves, timing, dimming, and parallax are tuned per release and respect system settings like Reduce Motion.
slideLeft is a fixed curve that ages with the codebase and looks the same — i.e. wrong — on both platforms.
- Zero-maintenance upgrades: when the OS redesigns navigation (as iOS 26 did), default transitions get it for free.
slideLeft is the classic trap: it approximates the iOS push, so it survives review — but it's an approximation running against the real thing every user knows by feel.
The legitimate exceptions
Explicit options are for suppressing animation in programmatic navigation, not restyling it:
| Situation |
Option |
Why |
| Initial/setup navigation the user never "performs" — populating tab outlets on startup, restoring state, landing a deep link |
{ animated: false } |
Nothing should appear to move; the user didn't navigate. |
| Auth → main app swap, logout resets |
{ clearHistory: true, animated: false } |
A world-switch, not a push; back should not return. |
| Overlays, pickers, flows that sit on top of context |
Modal (showModal, or a flavor's dialog service) |
Modality is a different navigation semantic — don't fake it with a transition on a push. |
| A deliberate, designed moment (splash → home crossfade, shared-element showcase) |
Custom transition |
Fine when the design calls for non-navigation motion. Rare, intentional, usually one place in the app — never the default for ordinary push/pop. |
If you're typing transition: { name: 'slide…' } on a plain forward navigation, stop — that's the platform's job.
Review checklist
grep -rn "transition" src --include="*.ts" — each hit must be one of the exceptions above, with a reason; ordinary pushes carry no transition.
animated: false only on navigations the user didn't initiate.
- On device: push then edge-swipe back on iOS (page and nav bar must track the finger together); on Android check predictive back preview. If either looks detached, a custom transition is likely interfering.
Verified 2026-08: iOS 26 simulator + Android 15 emulator, @nativescript/core 9.1.0-alpha.11 (edge-swipe/predictive-back behavior checked with and without custom transitions in a production app).
1---2name: ns-navigation-transitions3description: Use when writing or reviewing any Frame.navigate / RouterExtensions.navigate call, when a push/back animation looks flat, janky, or "web-like", or when deciding between animated false, clearHistory, or a custom transition — omit explicit transition options so pages animate with the platform-default navigation transition.4license: Apache-2.05---67# Navigation transitions: default to the platform89Every `Frame.navigate()` (and `RouterExtensions.navigate()` in Angular) accepts a `transition` option. The correct value, almost always, is **none at all**. Omit it and NativeScript hands the navigation to the OS — `UINavigationController`'s push/pop on iOS, the native fragment transition (with predictive back) on Android.1011```ts12// WRONG: downgrades the experience — looks close to native, feels wrong13frame.navigate({14 moduleName: 'views/item/item-page',15 context: { id: item.id },16 transition: { name: 'slideLeft' },17});1819// RIGHT: platform-default animation20frame.navigate({21 moduleName: 'views/item/item-page',22 context: { id: item.id },23});24```2526## Why the default wins2728A custom `transition` (`slideLeft`, `fade`, `flip`, …) replaces the system's transition coordinator with a hand-rolled animator. What you lose:2930* **Coordinated chrome animation**: the system transition morphs the whole navigation context together — ActionBar/nav-bar crossfade, back-button label slide, Liquid Glass morphing on iOS 26, large-title collapse. A custom animator moves only the page; the chrome snaps.31* **Interactive gestures**: iOS edge-swipe pop and Android predictive back are *interactive, progress-driven* versions of the default transition. Custom transitions at best replay a canned animation, at worst fight the gesture.32* **Platform motion language**: each OS's curves, timing, dimming, and parallax are tuned per release and respect system settings like Reduce Motion. `slideLeft` is a fixed curve that ages with the codebase and looks the same — i.e. wrong — on both platforms.33* **Zero-maintenance upgrades**: when the OS redesigns navigation (as iOS 26 did), default transitions get it for free.3435`slideLeft` is the classic trap: it *approximates* the iOS push, so it survives review — but it's an approximation running against the real thing every user knows by feel.3637## The legitimate exceptions3839Explicit options are for **suppressing** animation in programmatic navigation, not restyling it:4041| Situation | Option | Why |42|---|---|---|43| Initial/setup navigation the user never "performs" — populating tab outlets on startup, restoring state, landing a deep link | `{ animated: false }` | Nothing should appear to move; the user didn't navigate. |44| Auth → main app swap, logout resets | `{ clearHistory: true, animated: false }` | A world-switch, not a push; back should not return. |45| Overlays, pickers, flows that sit *on top of* context | Modal (`showModal`, or a flavor's dialog service) | Modality is a different navigation semantic — don't fake it with a transition on a push. |46| A deliberate, designed moment (splash → home crossfade, shared-element showcase) | Custom `transition` | Fine when the *design* calls for non-navigation motion. Rare, intentional, usually one place in the app — never the default for ordinary push/pop. |4748If you're typing `transition: { name: 'slide…' }` on a plain forward navigation, stop — that's the platform's job.4950## Review checklist5152* `grep -rn "transition" src --include="*.ts"` — each hit must be one of the exceptions above, with a reason; ordinary pushes carry no `transition`.53* `animated: false` only on navigations the user didn't initiate.54* On device: push then edge-swipe back on iOS (page and nav bar must track the finger together); on Android check predictive back preview. If either looks detached, a custom transition is likely interfering.5556Verified 2026-08: iOS 26 simulator + Android 15 emulator, @nativescript/core 9.1.0-alpha.11 (edge-swipe/predictive-back behavior checked with and without custom transitions in a production app).