View Transition Browser Support Matrix

The View Transitions API landed in engines in stages — same-document transitions first, cross-document transitions a year later, and the selector-level refinements (view-transition-class, nested groups) later still — so “does the browser support View Transitions?” is never a yes/no question. This reference breaks support down feature by feature and engine by engine, documents the flags and the per-engine rendering quirks, and ends with a deployment sequence that ships transitions safely to every tier. It sits within the broader Scroll-Driven & View Transition Implementation Patterns section; for the snapshot-and-composite machinery these version numbers gate, see how the View Transition API works under the hood.

Articles in this guide


View Transition feature shipping timeline by engine A horizontal timeline from 2023 to 2026 with one row per engine. The Chromium row shows same-document transitions in Chrome 111 in early 2023, view-transition-class in Chrome 125 and cross-document transitions in Chrome 126 in mid 2024, and nested groups in newer releases in 2025. The Safari row shows same-document transitions in Safari 18 in late 2024 and cross-document transitions in Safari 18.2 shortly after. The Firefox row shows a dashed in-development segment behind a preference from 2025 onward. Chromium Safari Firefox Chrome 111 same-document Chrome 125 v-t-class Chrome 126 cross-document newer Chromium nested groups Safari 18 same-document Safari 18.2 cross-document behind pref, in development 2023 2024 2025 2026

Feature inventory and syntax reference

The API is not one feature but a family. Each row below shipped independently and must be detected independently:

Feature Surface What it does
document.startViewTransition(cb) JavaScript Snapshots the page, runs cb to mutate the DOM, animates old → new (same document)
::view-transition, ::view-transition-group(), ::view-transition-old(), ::view-transition-new() CSS pseudo-elements The generated tree you style to customize the animation
view-transition-name CSS property Tags an element for an independent transition group
@view-transition { navigation: auto; } CSS at-rule Opts a document into cross-document transitions on same-origin navigation
view-transition-class CSS property Lets one pseudo-element rule target many named groups
view-transition-group (property) CSS property Nests transition groups so children clip and transform with their parent snapshot
startViewTransition({ update, types }) JavaScript Typed transitions; :active-view-transition-type() matches in CSS

The distinction that matters most for support planning is same-document versus cross-document, because they shipped roughly a year apart in both Chromium and WebKit. The same-document vs cross-document View Transitions guide covers the behavioral differences; the tables below cover only availability.

Same-document support matrix

Same-document transitions — document.startViewTransition() plus the ::view-transition-* pseudo-element tree — have the widest coverage:

Engine / browser First stable version Status Notes
Chrome 111 (March 2023) Shipped Full pseudo-element tree, transition.finished / .ready / .updateCallbackDone promises
Edge 111 (March 2023) Shipped Tracks Chromium; identical behavior
Opera 97 Shipped Chromium-based; follows Chrome 111
Samsung Internet 23 Shipped Chromium-based; verify on-device, as GPU snapshot limits differ on low-end hardware
Safari 18 (September 2024) Shipped macOS Sequoia / iOS 18; earlier versions have no API at all
Firefox — In development Implementation in progress behind a preference in Nightly builds (targeted at the Firefox 144+ line); treat as unsupported in production

Practical reading of this table: Chromium coverage is deep (three-plus years of stable releases), Safari coverage starts abruptly at 18 with nothing before it, and Firefox must be treated as a no-support engine until its implementation ships and stabilizes. Any architecture that assumes startViewTransition exists will throw a TypeError for a meaningful share of users — which is why the wrapper pattern in detecting View Transition support with @supports and JavaScript is mandatory, not optional.

Cross-document @view-transition support

Cross-document transitions animate full multi-page navigations without JavaScript. Both the outgoing and incoming document must opt in with the at-rule, and the navigation must be same-origin:

Engine / browser First stable version Status Notes
Chrome 126 (June 2024) Shipped pageswap / pagereveal events for JS customization
Edge 126 (June 2024) Shipped Tracks Chromium
Safari 18.2 (December 2024) Shipped A narrow gap exists: Safari 18.0–18.1 supports same-document only
Firefox — Not shipped Same-document work must land first; treat as unavailable
/* Must appear in the CSS of BOTH documents involved in the navigation */
@view-transition {
  navigation: auto;
}

The at-rule is its own progressive-enhancement mechanism: an engine that does not recognize @view-transition drops the whole block during parsing and performs a normal, instant navigation. There is no error state and no broken layout — which makes cross-document transitions the safest View Transition feature to deploy, despite being the newest widely shipped one.

The Safari 18.0–18.1 window deserves emphasis because it breaks the assumption that same-document and cross-document support travel together. A user on iOS 18.1 gets your SPA page-swap animations but not your multi-page transitions. If your analytics show a significant share of un-updated iOS 18.0/18.1 devices, plan for that split tier explicitly.

view-transition-class and nested groups

Two refinements shipped after the core API and have their own support boundaries.

view-transition-class solves the selector explosion problem. Without it, styling forty card transitions requires forty ::view-transition-group(--card-1) … ::view-transition-group(--card-40) rules; with it, one class-based rule covers them all:

.card {
  /* each card still needs a unique name */
  view-transition-name: var(--card-name);
  /* but they share one class for styling */
  view-transition-class: card;
}

/* one rule targets every group carrying the class */
::view-transition-group(*.card) {
  animation-duration: 280ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}

Support: Chrome/Edge 125+, and Safari added it in the 18.x line alongside its cross-document work — verify against your minimum target Safari version rather than assuming it arrived in 18.0. Critically, @supports (view-transition-name: none) returns true in Chrome 111–124 where view-transition-class does not exist, so class-based rules silently fail to match there. Detect it separately with @supports (view-transition-class: none) if the styling difference matters.

Nested transition groups (the view-transition-group CSS property, distinct from the pseudo-element of the same name) let a child group render inside its parent’s group rather than in the flat top-level tree, so clipping, borders, and 3D transforms of the parent apply to the child snapshot during the animation. This is the newest piece of the family: it is available only in newer Chromium releases (Chrome 139-era and later) and should be treated as an experimental enhancement, not a dependency. The flat-tree behavior is the only portable assumption — a constraint that also shapes the comparison in View Transition API vs the FLIP technique, since hand-rolled FLIP keeps elements in the real DOM tree where ancestor clipping always applies.

Per-engine quirks

Version numbers say whether the API exists; these quirks say how it behaves once it does.

Snapshot rendering differences

  • Snapshots are flattened raster/replaced content. In every engine, ::view-transition-old() is a static capture and ::view-transition-new() renders as replaced content sized by object-fit-like rules. Text inside a snapshot does not reflow when the group’s size animates — it scales. Chromium and WebKit apply slightly different snapshot scaling filters, so text that scales during a transition can look crisper in one engine than the other at the same duration.
  • Ink overflow. Chromium captures ink overflow (box shadows, blur, outline) beyond the border box into the snapshot; WebKit’s capture bounds have differed in edge cases, so a heavy box-shadow on a morphing element is worth a visual check in Safari before shipping.
  • Root snapshot and fixed-position elements. The root group captures the full viewport, including position: fixed elements. A fixed header that should persist visually across the transition must be given its own view-transition-name in all engines, but the artifacts you get when you forget differ: Chromium tends to show the header crossfading with itself, while Safari builds have shown brief double-rendering. Name it explicitly everywhere.
  • Snapshot freezing. Video, canvas, and CSS animations inside a captured element freeze in the old snapshot in all engines, but whether the new side renders live during the animation is engine-dependent (Chromium renders the new side live; treat any mid-transition liveness as unspecified).

Iframe handling

  • Same-document transitions initiated inside an iframe run within that iframe’s own viewport in Chromium; the parent page is untouched.
  • Cross-document transitions do not fire for iframe navigations in any engine — @view-transition applies to top-level, same-origin traversals only.
  • An iframe that happens to sit inside a captured element is flattened into the snapshot like any other content: its internal scrolling and playback freeze on the old side for the duration of the transition. Cross-origin iframe content is captured as it was rendered, with no additional data exposed — but if the flash of frozen third-party UI is unacceptable, exclude the region from morphing by keeping it inside the root group.

Firefox (in development)

Firefox’s implementation can be enabled in Nightly via the dom.viewTransitions.enabled preference in about:config. Behind the flag, expect a partially complete feature: the core startViewTransition flow works, while newer pieces (types, view-transition-class, cross-document) trail behind. Never gate production code on the flag — its only legitimate use is pre-testing your fallback and enhancement paths against a fourth engine before Firefox ships.

Flags and pre-release channels

Channel How to enable What you get
Firefox Nightly about:config → dom.viewTransitions.enabled → true In-development same-document implementation
Chrome Canary / Dev chrome://flags → “Experimental Web Platform features” Spec features not yet stable (nested groups before their release, newer typed-transition behavior)
Safari Technology Preview Develop menu → Feature Flags WebKit’s in-progress View Transition refinements

Flags are for testing your guards, not for users. The four tiers you should actually test against are: full support (current Chrome/Edge), same-document-only (Safari 18.0–18.1), flagged/partial (Firefox Nightly), and none (Firefox stable, older Safari).

@supports guard recipes

CSS-side detection uses property support as a proxy for the whole same-document feature, following the same discipline as the site-wide browser support and progressive enhancement guide:

/* Baseline: navigation works, elements are visible, no transition styling */

/* Enhancement tier 1: same-document View Transitions */
@supports (view-transition-name: none) {
  .hero__title {
    view-transition-name: hero-title;
  }

  ::view-transition-old(hero-title),
  ::view-transition-new(hero-title) {
    animation-duration: 240ms;
  }
}

/* Enhancement tier 2: class-based group styling (newer engines only) */
@supports (view-transition-class: none) {
  .card {
    view-transition-class: card;
  }

  ::view-transition-group(*.card) {
    animation-duration: 280ms;
  }
}

/* Reduced motion wins over both tiers */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01ms !important;
  }
}

Why view-transition-name: none rather than a real name? none is the property’s initial value, so the query tests pure parser support without side effects, and it is valid in every engine that implements the property. An alternative form, @supports selector(::view-transition), tests the pseudo-element directly; it is useful when your enhancement is purely pseudo-element styling, but property-based queries are the more widely used convention and behave identically in practice.

Cross-document transitions need no guard at all — the @view-transition at-rule self-gates, as covered above. What you cannot do is detect the at-rule with @supports, because @supports tests declarations and selectors, not at-rules. If JavaScript needs to know whether cross-document transitions ran, listen for the pagereveal event and check event.viewTransition for a non-null value.

Safe progressive-deployment recipe

The sequence below ships View Transitions to every supporting tier with zero risk to the rest:

  1. Ship the baseline first. Every route change, DOM swap, and multi-page navigation must work with all View Transition code deleted. If removing the transition breaks navigation, the transition is load-bearing and the architecture is wrong.
  2. Add same-document CSS inside @supports (view-transition-name: none). Names, group styling, and custom keyframes all live in the guarded block. Non-supporting engines parse none of it.
  3. Wrap startViewTransition in a fallback function. Check 'startViewTransition' in document; when absent, invoke the DOM-update callback directly so the mutation still happens instantly. The full wrapper, including the reduced-motion guard and promise-shape compatibility, is built step by step in the detection and fallback article.
  4. Opt in to cross-document transitions with the at-rule only. No JS is required; add pageswap/pagereveal listeners later if you need per-navigation customization, and feature-test those events too ('onpagereveal' in window).
  5. Layer the reduced-motion override on top. Both the JS wrapper and the CSS pseudo-elements need it, because they are independent code paths.
  6. Verify each tier separately. Current Chrome (everything), Safari 18.0 (same-document only), Firefox stable (nothing — confirm the instant fallback), Firefox Nightly with the pref (early warning for engine differences).

Deployed in this order, each step is independently revertible and no step can regress a browser that a previous step already served.

Gotchas and failure modes

  1. @supports (view-transition-name: none) is true where view-transition-class is false. Chrome 111–124 and Safari 18.0 pass the first query and fail the second. Feature-detect at the granularity you consume; one guard per feature family, not one guard for “View Transitions.”
  2. Calling startViewTransition unguarded throws. document.startViewTransition(...) is a TypeError in Firefox stable and Safari 17. Because the DOM update lives inside the callback, an unguarded call means the navigation itself silently never happens — the worst possible failure mode. The JS guard is not cosmetic.
  3. Duplicate view-transition-name values skip the transition. If two rendered elements share a name at snapshot time, the transition aborts and the ready promise rejects. Engines agree on the abort but differ in console diagnostics; Chromium names the offending element, Safari is terser. Audit dynamically assigned names (list items, cards) most.
  4. Cross-document transitions require the at-rule on both sides. Declaring @view-transition on only the destination page yields a normal navigation with no error anywhere. When a cross-document transition “randomly” fails, check the outgoing page’s CSS first.
  5. UA sniffing ages badly here. Safari’s same-document/cross-document split (18.0 vs 18.2) and Firefox’s in-flight implementation mean any UA-string table you write is stale within months. Feature detection has no such decay.
  6. The flat pseudo-tree surprises clipped layouts. Portable behavior hoists every named group to the top-level tree, escaping ancestor overflow: hidden and border-radius. Nested groups fix this only in newer Chromium; if the design depends on clipping during the morph, you need the FLIP-style alternative or a design change.
  7. Flagged Firefox is not shipped Firefox. Behavior behind dom.viewTransitions.enabled can change before release. File it under “early signal,” never under “supported.”

Performance checklist

  • Keep the set of simultaneously named elements small — every view-transition-name allocates snapshot texture memory, and mobile GPUs (Samsung Internet’s typical hardware in particular) hit limits far earlier than desktops.
  • Skip startViewTransition() entirely under prefers-reduced-motion: reduce; CSS duration overrides alone still pay the snapshot cost.
  • Prefer transform/opacity keyframes on ::view-transition-group(); snapshot pseudo-elements composite cheaply, but animating their width/height still forces per-frame layout of the pseudo-tree.
  • Await transition.finished before scroll restoration or focus moves to avoid mid-animation reflows.
  • Clear dynamically assigned view-transition-name values after each transition so stale names cannot collide on the next one.
  • Test cross-document transitions on throttled networks: the incoming page must reach a render-blocking-complete state before the animation starts, so slow critical CSS visibly delays the transition rather than the transition hiding the load.
  • Profile in each engine tier, not just Chrome — Safari’s snapshot scaling and Chromium’s live-new-side rendering have different costs for the same markup.

Up: Scroll-Driven & View Transition Implementation Patterns