Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 11.5Intermediate ~19 min · shot 5 of 7

Skips, Errors and Fallbacks

In one line

A transition can be skipped by you, replaced by a newer one, rejected by a throwing update, abandoned for a duplicate name or a slow update, or never offered at all. Each case settles the three promises differently, and the DOM change must happen in all of them.

0/7 · 0%

Think of it like this

A timetable has a fast train with a dining car and a slow one that stops everywhere. If the fast train is cancelled, passengers still reach the city: only the dining car is lost. The transition is the dining car; your DOM change is the journey and must always happen.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

skipTransition()
Ends the animation at once and shows the new state. The update still runs.
AbortError
What ready rejects with when a transition is skipped or replaced.
unhandled rejection
A rejected promise nobody handles: Uncaught (in promise).
scoped transition
A transition started on an element, limited to that element's subtree.

Step by step

01Skip it, replace it

The first example skips a transition at once, then starts two in a row. Three updates ran in all (skipped, replaced, second), so the box toggled three times and ends wide: no update is lost.

02One guard for everything

The second example puts the API check, reduced motion and ready.catch in one helper. With the timeline's Reduced motion on, the log says transition: none, update ran directly and no tracks appear.

03Scope it

The third example starts the transition on a div: the tracks say div#scope::view-transition-… instead of html::view-transition-… (Pause, then Run again, then Step), and the outside button is still under the pointer during the animation.

04Know the slow case

An update that takes 5 seconds freezes the page, then ready rejects at 4 s (the code block below, measured in a plain page).

Try it yourself

  1. 1

    See the unhandled error

    In the skip example add document.startViewTransition(() => {}); document.startViewTransition(() => {}); after the closing })();. The console prints two lines (measured), each Uncaught (in promise) AbortError: Transition was skipped. New ViewTransition started: the first call was skipped by the second, and the second by the example's own calls that start after it. Add .ready.catch(() => {}) to both calls and they are gone (measured: 0 lines).

Code & diagrams

Skipped and replaced transitions

Each line settles all three promises. The last line proves both updates ran.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

skipTransition() at once | updateCallbackDone fulfilled | ready rejected AbortError: Transition was skipped. skipTransition() called | finished fulfilled
first, replaced | updateCallbackDone fulfilled | ready rejected AbortError: Transition was skipped. New ViewTransition started | finished fulfilled
second | updateCallbackDone fulfilled | ready fulfilled | finished fulfilled
box is wide: both updates ran
One guard: no API, reduced motion, skipped

Switch the timeline's Reduced motion on: the log changes and no transition tracks appear. The change itself always happens.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

reduced motion: false, transition: started
A scoped transition

The transition is limited to the dashed box. The first run plays at load, before you can pause: press Pause, then **Run again**, then Step. The tracks name `div#scope`, not `html`.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

element.startViewTransition is a function
under the outside button: BUTTON
pseudo-elements hang off: scope
An update that takes 5 seconds (measured in a plain page)js
const slow = document.startViewTransition(() => new Promise((resolve) => setTimeout(resolve, 5000)));
// ready rejected at 4 s:
//   updateCallbackDone  fulfilled
//   ready               rejected TimeoutError: Transition was aborted because of timeout in DOM update. DOM update timed out
//   finished            fulfilled

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use the document's transition for a small widget

In the scoped example change document.getElementById("scope").startViewTransition( to document.startViewTransition( in the async function at load (the first one).

terminal
$ Read the console
── what you'll see ──
under the outside button: HTML
pseudo-elements hang off: (empty: they are on the root element now)

Myth vs fact

Myth

If the transition is skipped the DOM change is skipped too.

Fact

Never. skipTransition() skips only the animation. The update callback runs in every case.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A UI that re-renders per keystroke or slider tick would snapshot each time, and a running transition blocks pointer events for its whole animation. Use transitions for clear, rare changes (a tab, a sort, a page), keep them near 200 ms on frequent controls, and give typing none.

Remember this

  1. 1

    The problem this solves: demos show the happy path. In production the visitor clicks twice, the update throws, two cards share a name, the browser is old, or the visitor asked for less motion. If your code assumes the animation always plays, the change can be lost or the console fills with unhandled errors.

  2. 2

    Recall from Topic 11.1: updateCallbackDone, ready and finished settle in that order for a transition that plays; this topic shows what each does when it does not.

  3. 3

    Your update runs in every case: skipped, replaced, timed out or abandoned for a duplicate name (all measured). It does not run only when the browser lacks startViewTransition, which is why Topic 11.1 guards the call.

  4. 4

    What each promise does. Skipped (skipTransition()) or replaced (a newer call): ready rejects with AbortError (Transition was skipped. skipTransition() called or ... New ViewTransition started), the other two fulfill. Duplicate names: ready rejects with InvalidStateError. Update slower than about 4 s: ready rejects with TimeoutError. Update throws or rejects: all three reject with that error. So ready is the one to catch, and finished means "the new state is live".

  5. 5

    **An uncaught ready is an error in the console:** Uncaught (in promise) AbortError: Transition was skipped. New ViewTransition started (measured). The page works, but the noise hides real errors: put t.ready.catch(() => {}) in the one helper all calls go through.

  6. 6

    Reduced motion belongs in the same guard: if (!document.startViewTransition || matchMedia('(prefers-reduced-motion: reduce)').matches) { update(); return; }. In CSS you can keep a quick fade instead (the Lumen build does). Either way the state change happens.

  7. 7

    Scoped transitions. element.startViewTransition(...) limits a transition to one subtree (Chromium 153 has it; MDN marks it experimental, not Baseline). Measured: the pseudo-elements hang off that element, with their own root, and the rest of the page stays clickable. document.activeViewTransition is the running transition or null (measured), so document.activeViewTransition?.skipTransition() skips whatever runs.

Explain it without notes

01

Which promise rejects when a transition is skipped, with which error, and does the DOM still update? Why wrap startViewTransition in one helper?

02

When would you use element.startViewTransition instead of the document's?

Practice

01

Write withTransition(update, types) that handles the missing API, reduced motion and skipped transitions, and returns the transition or null.

02

A search box re-renders results per keystroke. Apply a transition to a tab change only.

Done when you can

  • You can say how each failure settles the three promises

  • You can write the one guard: API, reduced motion and ready.catch

  • You can say when a scoped transition helps