View Transitions
Overview
Taxi can opt in to the browserâs native View Transitions API to animate page swaps, instead of (or in addition to) writing your own JS Transition.
Enable it with the enableViewTransitions option:
import { Core } from '@unseenco/taxi'
const taxi = new Core({
enableViewTransitions: true
})
When this is enabled and the browser supports it, Taxi wraps its DOM swap (removing the old data-taxi-view, inserting the new one) inside document.startViewTransition(). The browser then takes a screenshot of the page before and after the swap and animates between them - by default, a simple cross-fade.
Custom JS Transition classes are bypassed in favour of this browser-driven animation, but renderer lifecycle hooks (onLeave, onEnter, etc.) still run as normal.
Browsers without support simply fall back to Taxiâs regular behaviour, so this is safe to enable.
If the user has asked their system for reduced motion (prefers-reduced-motion: reduce), Taxi skips the View Transition and swaps the content instantly. Your JS Transition classes stay bypassed in that case too, so nothing animates.
Lifecycle timing
onEnter fires as soon as the new content is in the DOM (matching the browser starting its animation), but onEnterCompleted - and the NAVIGATE_END event - wait for the browserâs finished promise, i.e. until the animation has actually finished playing on screen. isTransitioning also stays true for that whole duration, so you can rely on it (or NAVIGATE_END) to know the animation is visually done, not just that the DOM has been swapped.
onLeave/onLeaveCompleted are unaffected and still fire immediately (before the browser even takes its âbeforeâ screenshot) - theyâre JS-side bookkeeping hooks, not part of the visual animation.
Can it transition just one element?
Yes. Give an element a unique view-transition-name and the browser pulls it out of the default cross-fade entirely, giving it its own âbeforeâ and âafterâ snapshot that it animates (morphs) between - regardless of where or how big it is on each page. Everything else on the page keeps using the default root transition.
.hero {
view-transition-name: hero;
}
As long as an element with view-transition-name: hero exists on both the page youâre leaving and the page youâre entering, the browser will morph one into the other (position, size, etc.) while the rest of the page just cross-fades. If you donât want an element to animate at all, set view-transition-name: none on it instead.
Live demo
Below is a small, self-contained example: a photo gallery where clicking a card navigates (via a real Taxi instance) to a detail page. Each photo shares the same view-transition-name between the gallery and detail views, so it morphs from its small square into the larger detail layout, while the heading/text around it simply fades.
Open the demo in a new tab â