CSS View Transitions Module Level 2

Editor’s Draft,

More details about this document
This version:
https://drafts.csswg.org/css-view-transitions-2/
Latest published version:
https://www.w3.org/TR/css-view-transitions-2/
Feedback:
CSSWG Issues Repository
Inline In Spec
Editors:
Noam Rosenthal (Google)
Khushal Sagar (Google)
Vladimir Levin (Google)
Tab Atkins-Bittner (Google)
Suggest an Edit for this Spec:
GitHub Editor

Abstract

This module defines how the View Transition API works with cross-document navigations.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-view-transitions” in the title, like this: “[css-view-transitions] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 03 November 2023 W3C Process Document.

1. Introduction

This section is non-normative.

View Transitions, as specified in [css-view-transitions-1], is a feature that allows developers to create animated transitions between visual states of the document.

Level 2 extends that specification, by adding the necessary API and lifecycle to enable transitions across a same-origin cross-document navigation.

1.1. Lifecycle

This section is non-normative.

A successful cross-document view transition goes through the following phases:

  1. The user navigates, by clicking a link, submitting a form, traversing history using the browser UI, etc.

  2. Once it’s time to unload the old document, if the navigation is same origin and the old Document has opted in to cross-document view-transitions, the old state is captured.

  3. An event named reveal is fired on the new Document, with a viewTransition property, which is a ViewTransition object. This ViewTransition's updateCallbackDone is already resolved, and its captured elements are populated from the old Document.

  4. Right before the new Document has the first rendering opportunity, its state is captured as the "new" state.

  5. From this point forward, the transition continues as if it was a same-document transition, as per activate view transition.

1.2. Examples

To generate the same cross-fade as in the first example CSS View Transitions 1 § 1.6 Examples, but across documents, we don’t need JavaScript.

Instead, we opt in to triggering view-transitions on navigation in both page 1 and page 2:

// in both documents:
@view-transition {
  navigation: auto;
}

A link from page 1 to or from page 2 would generate a crossfade transition for example 1. To achieve the effect examples 2, 3 & 4, simply put the CSS for the pseudo-elements in both documents.

To achieve the effect in example 5, we have to do several things:

In both pages:

@view-transition {
  navigation: auto;
}

In the old page:

addEventListener('click', event => {
  sessionStorage.setItem("lastClickX", event.clientX);
  sessionStorage.setItem("lastClickY", event.clientY);
});

In the new page:

// This would run both on initial load and on reactivation from BFCache.
addEventListener("pagereveal", async event => {
  if (!event.viewTransition)
    return;

  const x = sessionStorage.getItem("lastClickX") ?? innerWidth / 2;
  const y = sessionStorage.getItem("lastClickY") ?? innerHeight / 2;

  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  );

  await event.viewTransition.ready;

  // Animate the new document’s view
  document.documentElement.animate(
    {
      clipPath: [
        `circle(0 at ${x}px ${y}px)`,
        `circle(${endRadius}px at ${x}px ${y}px)`,
      ],
    },
    {
      duration: 500,
      easing: 'ease-in',
      pseudoElement: '::view-transition-new(root)'
    }
  );
})

2. Pseudo-classes

2.1. Active View Transition Pseudo-class :active-view-transition()'

The :active-view-transition(<vt-type-selector>) pseudo-class applies to the root element of the document, if it has a matching active view transition. It has the following syntax definition:

:active-view-transition(<vt-type-selector>)
<vt-type-selector> = '*' | <custom-ident>#

The specificity of an :active-view-transition() is one pseudo-class selector if it has value is *, and two if it has any other value.

An ::active-view-transition() pseudo-class matches the document element when it has an non-null active view transition viewTransition, for which any of the following are true:

For example, the developer might start a transition in the following manner:
document.startViewTransition({update: updateTheDOMSomehow, types: ["slide-in", "reverse"]});

This will activate any of the following '::active-view-transition()'' selectors:

:root:active-view-transition(slide-in) {}
:root:active-view-transition(reverse) {}
:root:active-view-transition(slide-in, reverse) {}
:root:active-view-transition(slide-in, something-else) {}
:root:active-view-transition(*) {}

While starting a transition without selecting transition types, would only activate '::active-view-transition() with *'':

document.startViewTransition(updateTheDOMSomehow);
// or
document.startViewTransition({update: updateTheDOMSomehow});
/* This would be active */
:root { }
:root:active-view-transition(*) {}

/* This would not be active */
:root:active-view-transition(slide-in) {}
:root:active-view-transition(any-type-at-all-except-star) {}

3. CSS rules

3.1. The @view-transition rule

The @view-transition rule is used by a document to indicate that cross-document navigations should setup and activate a ViewTransition. To take effect, it must be present in the old document when unloading, and in the new document when it is reveal.

3.2. @view-transition rule grammar

The @view-transition rule has the following syntax:

@view-transition {
  <declaration-rule-list>
}

Note: as per default behavior, the @view-transition rule can be nested inside a conditional group rule such as @media or @supports.

3.3. The navigation descriptor

Name: navigation
For: @view-transition
Value: auto | none
Initial: none

The 'navigation' descriptor opts in to automatically starting a view transition when performing a navigation of a certain type. It needs to be enabled both in the old document (when unloading) and in the new document (when reveal).

none

There will be no transition.

auto

The transition will be enabled if the navigation is same-origin, without cross-origin redirects, and is not a reload.

3.4. The type descriptor

Name: type
For: @view-transition
Value: none | <custom-ident>*
Initial: none

The 'type' descriptor sets the active types for the transition when capturing and performing the transition, equivalent to calling startViewTransition(callbackOptions) with that type.

4. API

4.1. The PageRevealEvent

Note: this should go in the HTML spec. See Issue 9315.

[Exposed=Window]
interface PageRevealEvent : Event {
  readonly attribute ViewTransition? viewTransition;
};

A PageRevealEvent has a null-or-ViewTransition view transition, initially null.

Note: this event is fired when the document is reveal.

The viewTransition getter steps are to return this's view transition.

4.2. Additions to Document

dictionary StartViewTransitionOptions {
  UpdateCallback? update = null;
  sequence<DOMString>? type = null;
};

partial interface Document {

  ViewTransition startViewTransition(optional (UpdateCallback or StartViewTransitionOptions) callbackOptions = {});
};

4.2.1. startViewTransition(callbackOptions) Method Steps

The method steps for startViewTransition(callbackOptions) are as follows:
  1. If callbackOptions is not provided, then run the method steps for startViewTransition() and return the result.

  2. If callbackOptions is an UpdateCallback, then run the method steps for startViewTransition(updateCallback) given callbackOptions and return the result.

  3. Let viewTransition be the result of running method steps for startViewTransition(updateCallback) given callbackOptions’s update.

  4. Set viewTransition’s active types to callbackOptions’s type.

  5. Return viewTransition.

4.3. Extensions to the CSSRule interface

The CSSRule interface is extended as follows:

partial interface CSSRule {
  const unsigned short VIEW_TRANSITION_RULE = 15;
};

The CSSViewTransitionRule represents a @view-transition rule.

enum ViewTransitionNavigation { "auto", "none" };
[Exposed=Window]
interface CSSViewTransitionRule : CSSRule {
  attribute ViewTransitionNavigation navigation;
  attribute DOMTokenList type;
};

5. Algorithms

5.1. Data Structures

5.1.1. Additions to Document

A Document additionally has:

pagereveal fired

a boolean, initially false.

5.1.2. Additions to ViewTransition

A ViewTransition additionally has:

is inbound cross-document transition

a boolean, initially false.

should a cross-document transition take precedent? See #9512

active types

Null or a list of strings, initially null.

5.2. Monkey patches to HTML

Prepend a step at the beginning of the task queued on navigable’s active window when applying the history step (14.11.1, here):

If changingNavigationContinuation update-only is false, then setup cross-document view-transition given oldDocument, newDocument, navigationType, and the remaining steps and return from these steps.

Note: This would wait until a transition is captured or skipped before proceeding to unloading the old document and activating the new one.

Run the following step in updating the renedering, before running the animation frame callbacks:
  1. For each fully active Document doc in docs, run the reveal steps for doc.

Run the following step at the end of reactivate:
  1. Set document’s pagereveal fired to false.

To reveal Document document:
  1. If document’s page pagereveal fired is true, then return.

  2. Let transition be the result of resolving cross-document view-transition for document.

  3. Fire an event named pagereveal at document’s relevant global object, using PageRevealEvent, with view transition initialized to transition.

  4. If transition is not null, then activate transition.

  5. Set document’s page pagereveal fired to true.

5.3. Setting up and activating the cross-document view transition

5.3.1. Resolving the @view-transition' rule

To get the resolve @view-transition rule for a Document document:
  1. Let matchingRule be the last @view-transition rule in document.

  2. If matchingRule is not found, then return "skip transition".

  3. If matchingRule’s navigation descriptor’s computed value is none, then return "skip transition".

  4. Assert: matchingRule’s navigation descriptor’s computed value is auto.

  5. Let typesDescriptor be matchingRule’s type descriptor.

  6. If typesDescriptor’s computed value is none, then return an empty list.

  7. Return a list of strings corresponding to typesDescriptor’s computed value.

5.3.2. Setting up the view-transition in the old Document

To setup cross-document view-transition given a Document oldDocument, a Document newDocument, a NavigationType navigationType, and onReady, which is an algorithm accepting nothing:
  1. If the user agent decides to display an implementation-defined navigation experience, e.g. a gesture-based transition for a back navigation, the user agent may ignore the author-defined view transition. If that is the case, return.

  2. If navigationType is reload, then return.

  3. If oldDocument’s origin is not same origin as newDocument’s origin then call onReady and return.

  4. If newDocument was created via cross-origin redirects is true and newDocument’s latest entry is null, then call onReady and return.

    Note: A document with a non-null latest entry is being reactivated, in which case we don’t need to check for cross-origin redirects.

  5. Resolve @view-transition rule for oldDocument and let resolvedRule be the result.

  6. If resolvedRule is "skip transition", then call onReady and return.

    Note: We don’t know yet if newDocument has opted in, as it might not be parsed yet. We check the opt-in for newDocument when it is reveal.

  7. If oldDocument’s active view transition is not null, then skip oldDocument’s active view transition with an "AbortError" DOMException in oldDocument’s relevant Realm.

    Note: this means that any running transition would be skipped when the document is ready to unload.

  8. Set document’s auto-skip view transitions to true.

    Note: this means that calling startViewTransition() while capturing the old document for a cross-document view-transition would run the callback but skip the animation.

  9. Let outboundTransition be a new ViewTransition object in oldDocument’s relevant Realm, whose active types is resolvedRule, and whose process old state captured is set to the following steps:

    should we check for the opt-in again, in case there was a CSSOM change in a requestAnimationFrame callback?

    1. If outboundTransition’s phase is "done", then call onReady and return.

    2. Assert: outboundTransition’s phase is "pending-capture".

    3. Clear view transition outboundTransition.

      Note: The ViewTransition object on the old Document should be destroyed after its state has been copied to the new Document below. We explicitly clear it here since the old Document may be cached by the UA.

    4. Set document’s auto-skip view transitions to false.

    5. Queue a global task on the DOM manipulation task source given newDocument’s relevant global object, to perform the following steps:

      1. Let inboundTransition be a new ViewTransition in newDocument’s relevant Realm, whose named elements is outboundTransition’s named elements, initial snapshot containing block size is outboundTransition’s initial snapshot containing block size, and whose is inbound cross-document transition is true.

      2. Let newDocument’s active view transition be inboundTransition.

      3. Call the update callback for inboundTransition.

      4. Call onReady.

      5. At any given time, the UA may decide to skip inboundTransition, e.g. after an implementation-defined timeout. To do so, the UA should queue a global task on the DOM manipulation task source given document’s relevant global object to perform the following step: If transition’s phase is not "done", then skip the view transition transition with a "TimeoutError" DOMException.

    Note: outboundTransition is not exposed to JavaScript, it is used only for capturing the state of the old document.

  10. Set oldDocument’s active view transition to outboundTransition.

    Note: The process continues in setup view transition, via perform pending transition operations.

  11. The user agent should display the currently displayed frame until either:

    Note: this is to ensure that there are no unintended flashes between displaying the old and new state, to keep the transition smooth.

5.3.3. Accessing the view-transition in the new Document

To resolve cross-document view-transition for Document document:
  1. Let transition be document’s active view transition.

  2. If transition is null or transition’s is inbound cross-document transition is false, then return null.

    Note: transition’s is inbound cross-document transition would be false if a same-document transition was started before the page was reveal.

  3. Resolve @view-transition rule for document and let resolvedRule be the result.

  4. If resolvedRule is "skip transition", then skip transition and return null.

  5. Set transition’s active types to resolvedRule.

  6. Return transition.

Privacy Considerations

This specification introduces no new privacy considerations.

Security Considerations

To prevent cross-origin issues, at this point cross-document view transitions can only be enabled for same-origin navigations. As discussed in WICG/view-transitions#200, this still presents two potential threats:

  1. The cross-origin isolated capability in both documents might be different. This can cause a situation where a Document that is cross-origin isolated can read image data from a document that is not cross-origin isolated. This is already mitigated in [[css-view-transitions-1#sec], as the same restriction applies for captured cross-origin iframes.

  2. A same-origin navigation might still occur via a cross-origin redirect, e.g. https://example.com links to https://auth-provider.com/ which redirects back to https://example.com/loggedin.

    This can cause a (minor) situation where the cross-origin party would redirect the user to an unexpected first-party URL, causing an unexpected transition and obfuscating that fact that there was a redirect. To mitigate this, currently view transitions are disabled for navigations if the Document was created via cross-origin redirects. Note that this check doesn’t apply when the Document is being reactivated, as in that case the cross-origin redirect has already taken place.

    Note: this only applies to server-side redirects. A client-side redirect, e.g. using [^meta/http-equiv/refresh^], is equivalent to a new navigation.

  3. This feature exposes more information to CSS, as so far CSS was not aware of anything navigation-related. This can raise concerns around safety 3rd-party CSS. However, as a general rule, 3rd-party stylesheets should come from trusted sources to begin with, as CSS can learn about the document or change it in many ways.

See Issue #8684 and WICG/view-transitions#200 for detailed discussion.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Implementations of Unstable and Proprietary Features

To avoid clashes with future stable CSS features, the CSSWG recommends following best practices for the implementation of unstable features and proprietary extensions to CSS.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at https://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. URL: https://drafts.csswg.org/css-cascade-5/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: https://drafts.csswg.org/css-syntax/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSS-VIEW-TRANSITIONS-1]
Tab Atkins Jr.; Jake Archibald; Khushal Sagar. CSS View Transitions Module Level 1. URL: https://drafts.csswg.org/css-view-transitions-1/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SELECTORS-3]
Tantek Çelik; et al. Selectors Level 3. URL: https://drafts.csswg.org/selectors-3/
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4. URL: https://drafts.csswg.org/selectors/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[CSS-CONDITIONAL-3]
David Baron; Elika Etemad; Chris Lilley. CSS Conditional Rules Module Level 3. URL: https://drafts.csswg.org/css-conditional-3/

Property Index

No properties defined.

@view-transition Descriptors

Name Value Initial
navigation auto | none none
type none | <custom-ident>* none

IDL Index

[Exposed=Window]
interface PageRevealEvent : Event {
  readonly attribute ViewTransition? viewTransition;
};

dictionary StartViewTransitionOptions {
  UpdateCallback? update = null;
  sequence<DOMString>? type = null;
};

partial interface Document {

  ViewTransition startViewTransition(optional (UpdateCallback or StartViewTransitionOptions) callbackOptions = {});
};

partial interface CSSRule {
  const unsigned short VIEW_TRANSITION_RULE = 15;
};

enum ViewTransitionNavigation { "auto", "none" };
[Exposed=Window]
interface CSSViewTransitionRule : CSSRule {
  attribute ViewTransitionNavigation navigation;
  attribute DOMTokenList type;
};

Issues Index

should a cross-document transition take precedent? See #9512
should we check for the opt-in again, in case there was a CSSOM change in a requestAnimationFrame callback?
MDN

Document/startViewTransition

In only one current engine.

FirefoxNoneSafariNoneChrome111+
Opera?Edge111+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?