<pre class='metadata'>
Title: CSS View Transitions Module Level 2
Shortname: css-view-transitions
Level: 2
Status: ED
Group: csswg
Prepare for TR: no
ED: https://drafts.csswg.org/css-view-transitions-2/
TR: https://www.w3.org/TR/css-view-transitions-2/
Work Status: exploring
Editor: Noam Rosenthal, Google, w3cid 121539
Editor: Khushal Sagar, Google, w3cid 122787
Editor: Vladimir Levin, Google, w3cid 75295
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
Abstract: This module defines the View Transition API, along with associated properties and pseudo-elements,
	which allows developers to create animated visual transitions representing changes in the document state.
Markup Shorthands: css yes, markdown yes
</pre>

<pre class=link-defaults>
spec:css-view-transitions-1;
	text: active view transition; type: dfn;
	text: capture the new state; type: dfn;
	text: capture the old state; type: dfn;
	text: activate view transition; type: dfn;
	text: skip the view transition; type: dfn;
	text: ViewTransition; type: interface;
	text: named elements; for: ViewTransition; type: dfn;
	text: finished promise; for: ViewTransition; type: dfn;
	text: ready promise; for: ViewTransition; type: dfn;
	text: update callback; for: ViewTransition; type: dfn;
	text: update callback done promise; for: ViewTransition; type: dfn;
	text: initial snapshot containing block size; for: ViewTransition; type: dfn;
	text: captured elements; type: dfn;
	text: snapshot containing block; type: dfn;
	text: old transform; for: captured element; type: dfn;
	text: new element; for: captured element; type: dfn;
	text: updateCallbackDone; type: property; for: ViewTransition;
	text: phase; type: dfn; for: ViewTransition;
	text: call the update callback; type: dfn;
	text: perform pending transition operations; type: dfn;
	text: setup view transition; type: dfn;
	text: named view transition pseudo-element; type: dfn;
	text: rendering suppression for view transitions; type: dfn;
	text: view transition tree; type: dfn;
	text: view transition name; type: dfn;
	text: group styles rule; type: dfn;
	text: update pseudo-element styles rule; type: dfn;
	text: document-scoped view transition name; type: dfn;
	text: global view transition user agent style sheet; type: dfn;
spec:dom; type:dfn; text:document; for:/
spec:css2; type:dfn; text:element
spec:html
	text: latest entry; type: dfn;
	text: has been revealed; type: dfn;
	text: render-blocking mechanism; type: dfn;
spec:geometry-1
	text:multiply; type:dfn;
	text:matrix; type:dfn;
spec:infra; type:dfn; text:list
spec:css-borders-4; type: property;
	text:border-radius;
	text:border-width;
spec:css-break-4; type:dfn; text:fragment
spec:css2; type:dfn; text:viewport
</pre>

<style>
	spec-scaler {
		display: block;
	}
	spec-scaler:not(:defined) > * {
		display: none;
	}
	.spec-slides {
		width: 100%;
		height: 100%;
		border: none;
		display: block;
	}
	.spec-slide-controls {
		text-align: center;
	}
	.main-example-video {
		display: block;
		width: 100%;
		max-width: 702px;
		height: auto;
		margin: 0 auto;
	}

	/* Put nice boxes around each algorithm. */
	[data-algorithm]:not(.heading) {
		padding: .5em;
		border: thin solid #ddd; border-radius: .5em;
		margin: .5em calc(-0.5em - 1px);
	}
	[data-algorithm]:not(.heading) > :first-child {
		margin-top: 0;
	}
	[data-algorithm]:not(.heading) > :last-child {
		margin-bottom: 0;
	}
	[data-algorithm] [data-algorithm] {
		margin: 1em 0;
	}
	pre {
		tab-size: 2;
	}
</style>
<script async type="module" src="diagrams/resources/scaler.js"></script>

# Introduction # {#intro}

	*This section is non-normative.*

	This specification introduces a DOM API and associated CSS features
	that allow developers to create animated visual transitions,
	called <dfn export>view transitions</dfn>
	between different states of a [=/document=] or an [=/element=],
	or between distinct same-origin documents.

## Level 2 Updates ## {#intro-l2}

	Level 2 defines the following,
	extending the model in [[CSS-VIEW-TRANSITIONS-1]]:

		* [[#cross-document-view-transitions|Cross-document view transitions]], including the ''@view-transition'' rule and the algorithms
			that enable the cross-document view transition lifecycle.
		* [[#selective-vt|Selective view transitions]], a way to match styles based on the existence of an [=active view transition=],
			and more specifically based on the active view transition being of a certain type.
		* [[#shared-style-with-vt-classes|Sharing styles between view transition pseudo-elements]], a way to declare a style once,
			and use it for multiple view transition pseudo-elements. This includes the 'view-transition-class' property, and [[#pseudo-element-class-additions|additions to named pseudo-elements]]
		* [[#view-transition-group-prop|Nested view transition groups]], a way to construct deeper hierarchies of view transition pseudo-elements,
			which in turn allows relative transforms and clipping of participating elements.
		* [[#scoped-vt|Scoped view transitions]], a way to perform view transitions within the scope of a DOM subtree.

## Separating Visual Transitions from DOM Updates ## {#separating-transitions}

	Traditionally, creating a visual transition between two document states
	required a period where both states were present in the DOM at the same time.
	In fact, it usually involved creating a specific DOM structure
	that could represent both states.
	For example, if one element was “moving” between containers,
	that element often needed to exist outside of either container for the period of the transition,
	to avoid clipping from either container or their ancestor elements.

	This extra in-between state often resulted in UX and accessibility issues,
	as the structure of the DOM was compromised for a purely-visual effect.

	[=View Transitions=] avoid this troublesome in-between state
	by allowing the DOM to switch between states instantaneously,
	then performing a customizable visual transition between the two states in another layer,
	using a static visual capture of the old state, and a live capture of the new state.
	These captures are represented as a tree of [=pseudo-elements=]
	(detailed in [[#view-transition-pseudos]]),
	where the old visual state co-exists with the new state,
	allowing effects such as cross-fading
	while animating from the old to new size and position.

## View Transition Customization ## {#customizing}

	By default, <code>element.{{Element/startViewTransition()}}</code>
	and <code>document.{{Document/startViewTransition()}}</code>
	create a [=view transition=] consisting of a cross-fade of the entire
	[=ViewTransition/root element=] between the two DOM states.

	Developers can instead choose which descendant elements are captured independently
	using the 'view-transition-name' CSS property,
	allowing these to be animated independently of the rest of the page.
	Since the transitional state (where both old and new visual captures exist)
	is represented as pseudo-elements,
	developers can customize each transition using familiar features
	such as <a href="https://www.w3.org/TR/css-animations/">CSS Animations</a>
	and <a href="https://www.w3.org/TR/web-animations/">Web Animations</a>.

## View Transition Lifecycle ## {#lifecycle}

	A successful [=view transition=] goes through the following phases:

	1. Developer calls <code>document.{{Document/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>
		or <code>element.{{Element/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>,
		which returns a {{ViewTransition}}, <var>viewTransition</var>.

	1. Current state is captured as the “old” state.

	1. Rendering is paused within the view transition [=ViewTransition/root element=].

	1. Developer's {{ViewTransitionUpdateCallback|updateCallback}} function, if provided, is called,
		which updates the document state.

	1. <code><var>viewTransition</var>.{{ViewTransition/updateCallbackDone}}</code> fulfills.

	1. Current state is captured as the “new” state.

	1. Transition pseudo-elements are created.
		See [[#view-transition-pseudos]] for an overview of this structure.

	1. Rendering is unpaused, revealing the transition pseudo-elements.

	1. <code><var>viewTransition</var>.{{ViewTransition/ready}}</code> fulfills.

	1. Pseudo-elements animate until finished.

	1. Transition pseudo-elements removed.

	1. <code><var>viewTransition</var>.{{ViewTransition/finished}}</code> fulfills.


	<div id="phases-diagram">
		The following set of diagrams demonstrate the transition phases that a view transition goes through while performing the above steps:

		<spec-scaler canvaswidth="1920" canvasheight="1080" style="aspect-ratio: 1920/1080">
			<iframe class="spec-slides" src="diagrams/phases/phases.html"></iframe>
		</spec-scaler>
		<p class="spec-slide-controls">
			<button disabled>Previous</button>
			<button disabled>Next</button>
		</p>
		<script type="module">
			const root = document.querySelector('#phases-diagram');
			const [previous, next] = root.querySelectorAll('.spec-slide-controls button');
			const iframe = root.querySelector('iframe');
			next.disabled = false;

			const updateButtons = (slide) => {
				next.disabled = !slide.hasNext;
				previous.disabled = !slide.hasPrevious;
			};

			next.addEventListener('click', async () => {
				const slide = iframe.contentDocument.querySelector('spec-slide');
				await slide.next();
				updateButtons(slide);
			});
			previous.addEventListener('click', async () => {
				const slide = iframe.contentDocument.querySelector('spec-slide');
				await slide.previous();
				updateButtons(slide);
			});
		</script>
	</div>

## Transitions as an enhancement ## {#transitions-as-enhancements}

	A key part of the View Transition API design
	is that an animated transition is a visual <em>enhancement</em>
	to an underlying document state change.
	That means a failure to create a visual transition,
	which can happen due to misconfiguration or device constraints,
	will not prevent the developer's {{ViewTransitionUpdateCallback}} being called,
	even if it's known in advance that the transition animations cannot happen.

	For example, if the developer calls {{ViewTransition/skipTransition()}} at the start of the [[#lifecycle|view transition lifecycle]],
	the steps relating to the animated transition,
	such as creating the [=view transition tree=],
	will not happen.
	However, the {{ViewTransitionUpdateCallback}} will still be called.
	It's only the visual transition that's skipped,
	not the underlying state change.

	Note: If the DOM change should also be skipped,
	then that needs to be handled by another feature.
	<code>{{NavigateEvent|navigateEvent}}.{{NavigateEvent/signal}}</code> is an example of a feature developers could use to handle this.

	Although the View Transition API allows DOM changes to be asynchronous via the {{ViewTransitionUpdateCallback}},
	the API is not responsible for queuing or otherwise scheduling DOM changes
	beyond any scheduling needed for the transition itself.
	Some asynchronous DOM changes can happen concurrently (e.g if they're happening within independent components),
	whereas others need to queue, or abort an earlier change. The callback execution order is only specified for
	callbacks that result from view transitions on the same root element, or on elements related by DOM ancestors.
	In general, the scheduling order is best left to a feature or framework that has a more holistic view of the application.

## Rendering Model ## {#rendering-model}

	View Transition works by replicating an element's rendered state using UA generated pseudo-elements.
	Aspects of the element's rendering which apply to the element itself or its descendants,
	for example visual effects like 'filter' or 'opacity' and clipping from 'overflow' or 'clip-path',
	are applied when generating its image in [=Capture the image=].

	However, properties like 'mix-blend-mode' which define how the element draws when it is embedded can't be applied to its image.
	Such properties are applied to the element's corresponding ''::view-transition-group()'' pseudo-element,
	which is meant to generate a box equivalent to the element.

	If the ''::view-transition-group()'' has a corresponding element in the "new" states,
	the browser keeps the properties copied over to the ''::view-transition-group()'' in sync with the DOM element in the "new" state.
	If the ''::view-transition-group()'' has corresponding elements both in the "old" and "new" state,
	and the property being copied is interpolatable,
	the browser also sets up a default animation to animate the property smoothly.

## Examples ## {#examples}

	<div class=example>
		Taking a page that already updates its content using a pattern like this:

		```js
		function spaNavigate(data) {
			updateTheDOMSomehow(data);
		}
		```

		A [=view transition=] could be added like this:

		```js
		function spaNavigate(data) {
			// Fallback for browsers that don't support this API:
			if (!document.startViewTransition) {
				updateTheDOMSomehow(data);
				return;
			}

			// With a transition:
			document.startViewTransition(() => updateTheDOMSomehow(data));
		}
		```

		This results in the default transition of a quick cross-fade:

		<figure>
			<video src="diagrams/videos/default.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>

		The cross-fade is achieved using CSS animations on a [[#view-transition-pseudos|tree of pseudo-elements]],
		so customizations can be made using CSS. For example:

		```css
		::view-transition-old(root),
		::view-transition-new(root) {
			animation-duration: 5s;
		}
		```

		This results in a slower transition:

		<figure>
			<video src="diagrams/videos/slow.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>
	</div>

	<div class=example>
		Building on the previous example, motion can be added:

		```css
		@keyframes fade-in {
			from { opacity: 0; }
		}

		@keyframes fade-out {
			to { opacity: 0; }
		}

		@keyframes slide-from-right {
			from { transform: translateX(30px); }
		}

		@keyframes slide-to-left {
			to { transform: translateX(-30px); }
		}

		::view-transition-old(root) {
			animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
				300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
		}

		::view-transition-new(root) {
			animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
				300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
		}
		```

		Here's the result:

		<figure>
			<video src="diagrams/videos/slide.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>
	</div>

	<div class=example>
		Building on the previous example,
		the simple "whole document" transition displayed a visible judder in the purple heading bar,
		and had the heading text always slide to the left
		even when it was "moving" to the right in the 1->2 transition.
		To fix these issues,
		the header and text within the header can be given their own ''view-transition-name'' for the transition,
		which creates a separate ''::view-transition-group()'':

		```css
		.main-header {
			view-transition-name: main-header;
		}

		.main-header-text {
			view-transition-name: main-header-text;
			/* Give the element a consistent size, assuming identical text: */
			width: fit-content;
		}
		```

		By default, these groups will transition size and position from their “old” to “new” state,
		while their visual states cross-fade:

		<figure>
			<video src="diagrams/videos/header.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>
	</div>

	<div class=example>
		Building on the previous example, let's say some pages have a sidebar:

		<figure>
			<video src="diagrams/videos/bad-sidebar.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>

		In this case, things would look better if the sidebar was static if it was in both the “old” and “new” states.
		Otherwise, it should animate in or out.

		The '':only-child'' pseudo-class can be used to create animations specifically for these states:

		```css
		.sidebar {
			view-transition-name: sidebar;
		}

		@keyframes slide-to-right {
			to { transform: translateX(30px); }
		}

		/* Entry transition */
		::view-transition-new(sidebar):only-child {
			animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
				300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
		}

		/* Exit transition */
		::view-transition-old(sidebar):only-child {
			animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
				300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
		}
		```

		For cases where the sidebar has both an “old” and “new” state, the default animation is correct.

		<figure>
			<video src="diagrams/videos/good-sidebar.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>
	</div>

	<div class=example>
		Not building from previous examples this time,
		let's say we wanted to create a circular reveal from the user's cursor.
		This can't be done with CSS alone.

		Firstly, in the CSS, allow the “old” and “new” states to layer on top of one another without the default blending,
		and prevent the default cross-fade animation:

		```css
		::view-transition-image-pair(root) {
			isolation: auto;
		}

		::view-transition-old(root),
		::view-transition-new(root) {
			animation: none;
			mix-blend-mode: normal;
		}
		```

		Then, the JavaScript:

		```js
		// Store the last click event
		let lastClick;
		addEventListener('click', event => (lastClick = event));

		function spaNavigate(data) {
			// Fallback for browsers that don't support this API:
			if (!document.startViewTransition) {
				updateTheDOMSomehow(data);
				return;
			}

			// Get the click position, or fallback to the middle of the screen
			const x = lastClick?.clientX ?? innerWidth / 2;
			const y = lastClick?.clientY ?? innerHeight / 2;
			// Get the distance to the furthest corner
			const endRadius = Math.hypot(
				Math.max(x, innerWidth - x),
				Math.max(y, innerHeight - y)
			);

			// Create a transition:
			const transition = document.startViewTransition(() => {
				updateTheDOMSomehow(data);
			});

			// Wait for the pseudo-elements to be created:
			transition.ready.then(() => {
				// Animate the root's new 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',
						// Specify which pseudo-element to animate
						pseudoElement: '::view-transition-new(root)',
					}
				);
			});
		}
		```

		And here's the result:

		<figure>
			<video src="diagrams/videos/circle.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
		</figure>
	</div>

# API # {#api}

## Additions to {{Document}} ## {#additions-to-document-api}

	<xmp class=idl>
		partial interface Document {
			ViewTransition startViewTransition(
				optional (ViewTransitionUpdateCallback or StartViewTransitionOptions) callbackOptions = {}
			);
			readonly attribute ViewTransition? activeViewTransition;
		};

		callback ViewTransitionUpdateCallback = Promise<any> ();

		dictionary StartViewTransitionOptions {
			ViewTransitionUpdateCallback? update = null;
			sequence<DOMString>? types = null;
		};

	</xmp>

	<dl class="domintro non-normative">
		: <code>{{ViewTransition|viewTransition}} = {{Document|document}}.{{Document/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>
		:: Starts a new [=view transition=]
			(canceling the {{Document|document}}’s existing [=active view transition=], if any).

			{{ViewTransitionUpdateCallback|updateCallback}}, if provided, is called asynchronously, once the current state of the document is captured.
			Then, when the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} fulfills,
			the new state of the document is captured
			and the transition is initiated.

			Note that {{ViewTransitionUpdateCallback|updateCallback}}, if provided, is *always* called,
			even if the transition cannot happen.
			The transition is an enhancement around the state change, so a failure to create a transition never prevents the state change.
			See [[#transitions-as-enhancements]] for more details on this principle.

			If the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} rejects, the transition is skipped.
	</dl>

### {{Document/startViewTransition()}} Method Steps ### {#ViewTransition-prepare}

	<div algorithm="start-vt-with-options">
		The [=method steps=] for <dfn method for=Document>startViewTransition(|callbackOptions|)</dfn> are as follows:

		1. Perform the {{Element}} {{Element/startViewTransition()}} [=method steps=]
			given |callbackOptions|,
			but for the [=/root element|document root element=],
			and return the result.
	</div>

### {{Document/activeViewTransition}} Property ### {#doc-activeviewtransition}

In order to provide ergonomic behavior,
the [=active view transition=] is exposed to script via a document property.

<dl dfn-type=attribute dfn-for=Document>
	: <dfn>activeViewTransition</dfn>
	:: Returns the [=active view transition=] for the [=document=],
		or null if there is no active view transition.
</dl>

Note: An <code>element.startViewTransition()</code> call does not update
<code>document.activeViewTransition</code>, but will update
{{Element/activeViewTransition}} on the <code>element</code>.

## Additions to {{Element}} ## {#additions-to-element-api}

	<xmp class=idl>
		partial interface Element {
			ViewTransition startViewTransition(
				optional (ViewTransitionUpdateCallback or StartViewTransitionOptions) callbackOptions = {}
			);
			readonly attribute ViewTransition? activeViewTransition;
		};
	</xmp>

	<dl class="domintro non-normative">
		: <code>{{ViewTransition|viewTransition}} = {{Element|el}}.{{Element/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>
		:: Starts a new [=view transition=]
			(potentially canceling a conflicting [=view transition=]).

			{{ViewTransitionUpdateCallback|updateCallback}}, if provided, is called asynchronously, once the current state of the element and its subtree is captured.
			Then, when the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} fulfills,
			the new state of the element and its subtree is captured
			and the transition is initiated.

			Note that {{ViewTransitionUpdateCallback|updateCallback}}, if provided, is *always* called,
			even if the transition cannot happen.
			The transition is an enhancement around the state change, so a failure to create a transition never prevents the state change.
			See [[#transitions-as-enhancements]] for more details on this principle.

			If the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} rejects, the transition is skipped.
	</dl>

### {{Element/startViewTransition()}} Method Steps ### {#Element-ViewTransition-prepare}

	<div algorithm="start-el-vt-with-options">
		The [=method steps=] for <dfn method for=Element>startViewTransition(|callbackOptions|)</dfn> are as follows:

		1. Let |document| be [=this's=] [=relevant global object's=] [=associated document=].

		1. Let |viewTransition| be a new {{ViewTransition}} object in |document|'s [=relevant Realm=],
			with [=ViewTransition/root element=] set to [=this=].

		1. If |callbackOptions| is a {{ViewTransitionUpdateCallback}},
			set |viewTransition|'s [=update callback=] to |callbackOptions|.

			Otherwise, if |callbackOptions| is a {{StartViewTransitionOptions}}, then set |viewTransition|'s [=update callback=]
			to |callbackOptions|'s {{StartViewTransitionOptions/update}}.

		1. If [=this=]'s [=principal box=] is [=element-not-rendered|not rendered=],
			or is an <a spec="css-display-3">internal ruby box</a>
			or a <a spec="css-display-3" lt="atomic inline">non-atomic</a> <a spec="css-display-3">inline-level</a> box,
			[=Skip the view transition|skip=] |viewTransition| with an "{{InvalidStateError}}" {{DOMException}}.

		1. If [=this=] is not the [=/root element|document root element=],
			force [=this=] to have both [=layout containment=] and [=view transition scoping=] for the duration of the transition.

		1. If |document|'s [=active view transition=] is not null and its [=outbound post-capture steps=] is not null,
			then:

			1. [=Skip the view transition|Skip=] |viewTransition| with an "{{InvalidStateError}}" {{DOMException}}.

				Note: The |viewTransition|'s {{ViewTransition/types}} are ignored here because the transition is never activated.

			1. Return |viewTransition|.

			Note: This ensures that a same-document transition that started after firing {{Window/pageswap}} is skipped.

		1. If [=this=]'s [=active view transition=] is not null,
			then [=skip the view transition|skip that view transition=]
			with an "{{AbortError}}" {{DOMException}} in [=this's=] [=relevant Realm=].

			Note: This can result in two asynchronous [=ViewTransition/update callbacks=] running concurrently
			(and therefore possibly out of sequence):
			one for the [=this=]'s current [=active view transition=], and another for this |viewTransition|.
			As per the [design of this feature](#transitions-as-enhancements),
			it's assumed that the developer is using another feature or framework to correctly schedule these DOM changes.

		1. Set [=this=]'s [=active view transition=] to |viewTransition|.

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

		1. If |callbackOptions| is a {{StartViewTransitionOptions}}, set |viewTransition|'s [=ViewTransition/active types=] to a [=list/clone=] of {{StartViewTransitionOptions/types}} as a [=/set=].

		1. Return |viewTransition|.
	</div>

### {{Element/activeViewTransition}} Property ### {#element-activeviewtransition}

Similar to [Document.activeViewTransition](#doc-activeviewtransition),
the active view transition of an element is exposed to script via a property on the element.

<dl dfn-type=attribute dfn-for=Element>
	: <dfn>activeViewTransition</dfn>
	:: Returns the [=active view transition=] for the [=element=],
		or null if there is no active view transition.
</dl>

## The {{ViewTransition}} interface ## {#the-domtransition-interface}

	<xmp class=idl>
		[Exposed=Window]
		interface ViewTransition {
			readonly attribute Promise<undefined> updateCallbackDone;
			readonly attribute Promise<undefined> ready;
			readonly attribute Promise<undefined> finished;
			undefined skipTransition();
			[SameObject] readonly attribute ViewTransitionTypeSet types;
			readonly attribute Element transitionRoot;
			undefined waitUntil(Promise<any> promise);
		};
	</xmp>

	The {{ViewTransition}} interface represents and controls a single same-document [=view transition=],
	i.e. a transition where the starting and ending document are the same,
	possibly with changes to the document's DOM structure.

	<dl class="domintro non-normative">
		: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/updateCallbackDone}}</code>
		:: A promise that fulfills when the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} fulfills, or rejects when it rejects.

			Note: The View Transition API wraps a DOM change and creates a visual transition.
			However, sometimes you don't care about the success/failure of the transition animation,
			you just want to know if and when the DOM change happens.
			{{ViewTransition/updateCallbackDone}} is for that use-case.)

		: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/ready}}</code>
		:: A promise that fulfills once the pseudo-elements for the transition are created,
			and the animation is about to start.

			It rejects if the transition cannot begin.
			This can be due to misconfiguration, e.g. if {{ViewTransition/updateCallbackDone}} returns a rejected promise.

			The point that {{ViewTransition/ready}} fulfills
			is the ideal opportunity to animate the [=view transition pseudo-elements=]
			with the [[web-animations-1#extensions-to-the-element-interface|Web Animation API]].

		: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/finished}}</code>
		:: A promise that fulfills once the end state is fully visible and interactive to the user.

			It only rejects if {{ViewTransitionUpdateCallback|updateCallback}} returns a rejected promise,
			as this indicates the end state wasn't created.

			Otherwise, if a transition fails to begin,
			or is skipped (by {{ViewTransition/skipTransition()}}),
			the end state is still reached,
			so {{ViewTransition/finished}} fulfills.

		: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/skipTransition}}()</code>
		:: Immediately finish the transition, or prevent it starting.

			This never prevents {{ViewTransitionUpdateCallback|updateCallback}} being called,
			as the DOM change is independent of the transition.
			See [[#transitions-as-enhancements]] for more details on this principle.

			If this is called before {{ViewTransition/ready}} resolves, {{ViewTransition/ready}} will reject.

			If {{ViewTransition/finished}} hasn't resolved, it will fulfill or reject along with {{ViewTransition/updateCallbackDone}}.

		: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/transitionRoot}}</code>
		:: The {{ViewTransition}}'s [=ViewTransition/root element=].

			For [=view transitions=] started on the {{Document}},
			this is the [=document element=].

		: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/waitUntil}}()</code>
		:: Delays view transition finish until the given promise is settled

			When invoked with Promise |p|, run [=delay finish for promise=] steps with |p|.
	</dl>

	A {{ViewTransition}} has the following:

	<dl dfn-for="ViewTransition">
		: <dfn>named elements</dfn>
		:: a [=/map=], whose keys are [=view transition names=] and whose values are [=captured elements=].
			Initially a new [=map=].
			Note: Since this is associated to the {{ViewTransition}}, it will be cleaned up when [=Clear view transition=] is called.

		: <dfn>phase</dfn>
		:: One of the following ordered phases, initially "`pending-capture`":

			1. "`pending-capture`".
			1. "`update-callback-called`".
			1. "`animating`".
			1. "`done`".

			Note: For the most part, a developer using this API does not need to worry about the different phases, since they progress automatically.
			It is, however, important to understand what steps happen in each of the phases: when the snapshots are captured, when pseudo-element DOM is created, etc.
			The description of the phases below tries to be as precise as possible, with an intent to provide an unambiguous set of steps for implementors to follow in order to produce a spec-compliant implementation.

		: <dfn>update callback</dfn>
		:: a {{ViewTransitionUpdateCallback}} or null. Initially null.

		: <dfn>ready promise</dfn>
		:: a {{Promise}}.
			Initially [=a new promise=] in [=this's=] [=relevant Realm=].

		: <dfn>update callback done promise</dfn>
		:: a {{Promise}}.
			Initially [=a new promise=] in [=this's=] [=relevant Realm=].

			Note: The [=ready promise=] and [=update callback done promise=] are immediately created,
			so rejections will cause {{unhandledrejection}}s unless they're [=mark as handled|handled=],
			even if the getters such as {{updateCallbackDone}} are not accessed.

		: <dfn>finished promise</dfn>
		:: a {{Promise}}.
			Initially [=a new promise=] in [=this's=] [=relevant Realm=],
				[=marked as handled=].

			Note: This is [=marked as handled=] to prevent duplicate {{unhandledrejection}}s,
			as this promise only ever rejects along with the [=update callback done promise=].

		: <dfn>initial snapshot containing block size</dfn>
		:: a [=tuple=] of two numbers (width and height), or null.
			Initially null.

			Note: This is used to detect changes in the [=snapshot containing block size=],
			which causes the transition to [=skip the view transition|skip=].
			[Discussion of this behavior](https://github.com/w3c/csswg-drafts/issues/8045).

		: <dfn>active types</dfn>
		:: A {{ViewTransitionTypeSet}}, initially empty.

		: <dfn>outbound post-capture steps</dfn>
		:: Null or a set of steps, initially null.

		: <dfn>root element</dfn>
		:: An {{Element}},
			indicating which element is hosting the {{ViewTransition}}.
	</dl>

	The {{ViewTransition/finished}} [=getter steps=] are to return [=this's=] [=ViewTransition/finished promise=].

	The {{ViewTransition/ready}} [=getter steps=] are to return [=this's=] [=ViewTransition/ready promise=].

	The {{ViewTransition/updateCallbackDone}} [=getter steps=] are to return [=this's=] [=ViewTransition/update callback done promise=].

	The {{ViewTransition/types}} [=getter steps=] are to return [=this=]'s [=ViewTransition/active types=].

	The {{ViewTransition/transitionRoot}} [=getter steps=] are to return [=this=]'s [=ViewTransition/root element=].

### The {{ViewTransitionTypeSet}} Interface ### {#the-viewtransitiontypeset-interface}

	<xmp class=idl>
		[Exposed=Window]
		interface ViewTransitionTypeSet {
			setlike<DOMString>;
		};
	</xmp>

	The {{ViewTransitionTypeSet}} object represents a [=/set=] of strings, without special semantics.

	Note: a {{ViewTransitionTypeSet}} can contain strings that are invalid for '':active-view-transition-type'', e.g.
	strings that are not a <<custom-ident>>.

### {{ViewTransition/skipTransition()}} Method Steps ### {#ViewTransition-skipTransition}

	<div algorithm="dom-viewtransition-skipTransition">
		The [=method steps=] for <dfn method for="ViewTransition">skipTransition()</dfn> are:

		1. If [=this=]'s [=ViewTransition/phase=] is not "`done`",
			then [=skip the view transition=] for [=this=]
			with an "{{AbortError}}" {{DOMException}}.
	</div>


# CSS properties # {#css-properties}

## Tagging Individually Transitioning Subtrees: the 'view-transition-name' property ## {#view-transition-name-prop}

	<pre class=propdef>
	Name: view-transition-name
	Value: none | <<custom-ident>>
	Initial: none
	Inherited: no
	Percentages: n/a
	Computed Value: as specified
	Animation type: discrete
	</pre>

	Note: though 'view-transition-name' is [=discrete|discretely animatable=], animating it doesn't
	affect the running view transition. Rather, it's a way to set its value in a way that can change
	over time or based on a [=timeline=]. An example for using this would be to change the 'view-transition-name'
	based on [=scroll-driven animations=].

	The 'view-transition-name' property “tags” an element
	for [=capture in a view transition=],
	tracking it independently in the [=view transition tree=]
	under the specified <dfn>view transition name</dfn>.
	An element so captured is animated independently of the rest of the page.

	<dl dfn-type=value dfn-for=view-transition-name>
		: <dfn>none</dfn>
		:: The [=/element=] will not participate independently in a view transition.

		: <dfn><<custom-ident>></dfn>
		:: The [=/element=] participates independently in a view transition--
			as either an old or new [=/element=]--
			with the specified [=view transition name=].

			Each [=view transition name=] is a [=tree-scoped name=].

			The values <css>none</css>, <css>auto</css>, and <css>match-element</css> are excluded from <<custom-ident>> here.

			Note: If this name is not unique within a single transition
			(i.e. if two elements simultaneously specify the same [=view transition name=])
			then the [=view transition=] will abort. It is, however, possible to have non-unique
			[=view transition name=]s as long as the elements with these names are never part
			of the same transition.
	</dl>

	Note:  For the purposes of this API,
	if one element has [=view transition name=] ''foo'' in the old state,
	and another element has [=view transition name=] ''foo'' in the new state,
	they are treated as representing different visual states of the same element,
	and will be paired in the [=view transition tree=].
	This may be confusing, since the elements themselves are not necessarily referring to the same object,
	but it is a useful model to consider them to be visual states of the same conceptual page entity.

	If the element’s [=principal box=] is [=fragmented=],
	[=skips its contents|skipped=],
	or [=element-not-rendered|not rendered=],
	this property has no effect.
	See [[#algorithms]] for exact details.

	<div algorithm>
	To get the <dfn>document-scoped view transition name</dfn> for an {{Element}} |element|:

		1. Let |scopedViewTransitionName| be the [=computed value=] of 'view-transition-name' for |element|.

		1. If |scopedViewTransitionName| is associated with |element|'s [=node document=], then return |scopedViewTransitionName|.

		1. Otherwise, return ''view-transition-name/none''.
	</div>

### Rendering Consolidation ### {#named-and-transitioning}

	[=/Elements=] [=captured in a view transition=] during a [=view transition=]
	or whose 'view-transition-name' [=computed value=] is not ''view-transition-name/none'' (at any time):
	- Form a [=stacking context=].
	- Are [[css-transforms-2#grouping-property-values|flattened in 3D transforms]].
	- Form a [=backdrop root=].

## Isolating Scoped View Transition Names: the 'view-transition-scope' property ## {#view-transition-scope-prop}

	<pre class="propdef">
	Name: view-transition-scope
	Value: none | all
	Initial: none
	Inherited: no
	Percentages: n/a
	Computed Value: as specified
	Animation type: discrete
	</pre>

	Note: Although 'view-transition-scope' is [=discrete|discretely animatable=],
	it only has an effect during 'view-transition-name' discovery

	<dl dfn=type=value dfn-for=view-transition-scope>
		: <dfn>none</dfn>
		:: This property has no effect on 'view-transition-name' discoverability.

		: <dfn>all</dfn>
		:: Discoverability of 'view-transition-name' is limited to the subtree of this element.
			This is done by applying <dfn>view transition scoping</dfn>.
			Such scoping means that any transitions that originate outside of the element
			will not discover a non-none 'view-transition-name' either on this element
			or in its flat-tree descedants.

			<div class=example>
				```html
				<style>
				.scope { view-transition-scope: all; }
				#a { view-transition-name: a; }
				#b { view-transition-name: b; }
				#c { view-transition-name: c; }
				#d { view-transition-name: d; }
				</style>

				<div id=a>
					<div id=b class=scope>
						<div id=c></div>
					</div>
					<div id=d></div>
				</div>

				<script>
				...
				a.startViewTransition(); // Will discover "a" and "d" view-transition-names
				b.startViewTransition(); // Will discover "b" and "c" view-transition-names
				...
				</script>
				```
			</div>

			Note: Starting a transition implies that [=this=] element has ''view-transition-scope: all''
			for the duration of the transition.

			<div class=example>
				```html
				<style>
				#a { view-transition-name: a; }
				#b { view-transition-name: b; }
				#c { view-transition-name: c; }
				#d { view-transition-name: d; }
				</style>

				<div id=a>
					<div id=b>
						<div id=c></div>
					</div>
					<div id=d></div>
				</div>

				<script>
				...
				b.startViewTransition(); // Will discover "b" and "d" view-transition-names

				// While b's transition is active and running, the following will only discover
				// "a" and "d" view transition names, since b has a forced view-transition-scope: all.
				b.startViewTransition()
				...
				</script>
				```
			</div>
	</dl>

	Note: Most elements running or planning to run a [=scoped view transition=]
	should use ''view-transition-scope: all'', to ensure that their
	[=document-scoped view transition names=] don't accidentally interfere with
	other [=scoped view transitions=] in the ancestor chain.

## Nested View Transition Groups: The 'view-transition-group' property ## {#view-transition-group-prop}

	<pre class=propdef>
	Name: view-transition-group
	Value: normal | contain | nearest | <<custom-ident>>
	Initial: normal
	Inherited: no
	Percentages: n/a
	Computed Value: as specified
	Animation type: discrete
	</pre>

	The 'view-transition-group' property can be used in conjuction with 'view-transition-name' to generate a hierarchy of [=named view transition pseudo-element=].
	This property has no effect if 'view-transition-name' is ''view-transition-name/none''.

	<dl dfn-type=value dfn-for=view-transition-group>
		: <dfn>normal</dfn>
		:: The ''view-transition-group()'' [=pseudo-element=]
			generated by this element
			is a child of the ''view-transition-group-children()'' [=pseudo-element=]
			generated by this element's nearest ''view-transition-group()'' generating
			[=flat tree=] ancestor element
			with a non-''view-transition-group/normal'' 'view-transition-group' value,
			if any.
			If no such element is present, the ''view-transition-group()'' [=pseudo-element=]
			generated by this element
			is a direct child of the ''::view-transition'' [=pseudo-element=]
			associated with this view transition.
		: <dfn>contain</dfn>
		:: The ''view-transition-group-children()'' [=pseudo-element=]
			generated by this element acts as a container for its descendants.
			See ''view-transition-group/normal'' for the effect it has on other elements.
		: <dfn>nearest</dfn>
		:: The ''view-transition-group()'' [=pseudo-element=]
			generated by this element
			is a child of the ''view-transition-group-children()'' [=pseudo-element=]
			generated by this element's nearest ''view-transition-group()'' generating
			[=flat tree=] ancestor element,
			if any.
			If no such element is present, the ''view-transition-group()'' [=pseudo-element=]
			generated by this element
			is a direct child of the ''::view-transition'' [=pseudo-element=]
			associated with this view transition.
		: <dfn><<custom-ident>></dfn>
		:: The ''view-transition-group()'' [=pseudo-element=]
			generated by this element
			is a child of the ''view-transition-group()'' [=pseudo-element=]
			generated by this element's [=flat tree=] ancestor element
			with a ''view-transition-name'' value that matches '<<custom-ident>>', if any.
			If no such element is present, the ''view-transition-group()'' [=pseudo-element=]
			generated by this element
			is a direct child of the ''::view-transition'' [=pseudo-element=]
			associated with this view transition.
	</dl>

	The ''view-transition-group-children()'' [=pseudo-element=] is generated
	if and only if there is a ''view-transition-group()'' [=pseudo-element=] attached to it.

	The [=used value=] for 'view-transition-group' resolves to a 'view-transition-name' in its ancestor chain, or to ''view-transition-name/none''. When generating the [=named view transition pseudo-element=], the ''::view-transition-group()'' with that name
	would create a ''::view-transition-group-children()'' pseudo-element, which would be the parent of the ''::view-transition-group()'' generated for this element's 'view-transition-name'.

### Nested View Transition Group Explanation ### {#nested-overview}

*This section is non-normative.*

By default, setting `view-transition-name` on multiple elements generates a flat [=view transition tree=], where all the ''::view-transition-group()'' pseudo-elements are children of the ''::view-transition'' pseudo-element.
This is sufficient for many simple use-cases, but there are some styling use-cases that cannot be achieved with a flat tree.
The most prominent use-case is clipping: with a flat tree, all pseudo-elements are clipped to the [=snapshot containing block=], so clipping elements in the normal tree would lose their clipping during the view-transition, resulting in a broken visual effect.
The effects that have can have an unexpected visual effect in a flat tree:
* Clipping ('overflow', 'clip-path', 'border-radius'): clipping affects the children of the element.
* 'opacity', 'mask-image' and 'filter': These effects that are designed to work on a fully rasterized image of a tree, rather than on each item individually.
* 3D transforms ('transform-style', 'transform', 'perspective'): to display the full range of 3D transform animations, some hierarchy needs to be kept.

To enable these use cases, this specification introduces the concept of nesting view-transition pseudo-elements. By using the 'view-transition-group' CSS property,
the author can assign a "parent group" for a generated ''::view-transition-group()'' pseudo-element, creating a hierarchy in the [=view transition tree=].

### Examples ### {#nested-vt-example}

<div class="example">
This example creates a transition where the [=view transition tree=] is nested instead of flat:

```html
<section class="container">
	<article>Content</article>
</section>
```

```css
.container {
	view-transition-name: container;
}

.container,
::view-transition-group-children(container) {
	clip-path: circle();
}

article {
	view-transition-name: article;
	view-transition-group: container;
}
```

The pseudo-element tree for this would be as follows:
```
	::view-transition
	├─ ::view-transition-group(container)
	│  ├─ ::view-transition-image-pair(container)
	│  │  ├─ ::view-transition-old(container)
	│  │  └─ ::view-transition-new(container)
	│  └─ ::view-transition-group-children(container)
	│     ├─ ::view-transition-group(article)
	│     │  ├─ ::view-transition-old(article)
	│     │  └─ ::view-transition-new(article)
	│     └─ ...other nested groups...
	└─ …other groups…
```

By applying the ''clip-path'' to both the containing element and its generated ''::view-transition-group-children()'' pseudo-element, we preserve the clip during the transition,
and by applying ''view-transition-group'' to the internal element referencing the container, we make the tree "nested" in a way that would apply this clipping.
</div>

# Scoped View Transitions # {#scoped-vt}

	In addition to [=view transitions=] triggered on the [=document=] itself
	(<dfn export>global view transitions</dfn>),
	individual [=elements=] can host their own <dfn export>scoped view transitions</dfn>.
	This enables multiple [=view transitions=] to run on a page at the same time,
	with each animating a different subtree of the document.
	It also enables a [=view transition=] to be affected by the layout and rendering of its subtree,
	being affected by scrolling, filters, 'z-index', etc.
	("Global" [=view transitions=] are always rendered on a separate, higher rendering layer
	than the rest of the document.)

	In order to host a [=scoped view transition=],
	an element must have [=layout containment=],
	so its painted output can be captured as an atomic unit.

	[=View transitions=] whose [=ViewTransition/root element=] is the [=document element=]
	are <em>not</em> [=scoped view transitions=].
	They're [=global view transitions=].

# Pseudo-elements # {#pseudo}

## Pseudo-element Trees ## {#pseudo-root}

	Note: This is a general definition for trees of pseudo-elements. If other features need this behavior, these definitions will be moved to [[css-pseudo-4]].

	A <dfn>pseudo-element root</dfn> is a type of [=tree-abiding pseudo-element=] that is the [=tree/root=] in a [=tree=] of [=tree-abiding pseudo-elements=],
	known as the <dfn>pseudo-element tree</dfn>.

	The [=pseudo-element tree=] defines the document order of its [=tree/descendant=] [=tree-abiding pseudo-elements=].

	When a [=pseudo-element=] [=tree/participates=] in a [=pseudo-element tree=],
	its [=originating pseudo-element=] is its [=tree/parent=].

	If a [=tree/descendant=] |pseudo| of a [=pseudo-element root=] has no other [=tree/siblings=],
	then '':only-child'' matches that |pseudo|.

	Note: This means that `::view-transition-new(ident):only-child` will only select `::view-transition-new(ident)` if the parent `::view-transition-image-pair(ident)` contains a single [=tree/child=].
	As in, there is no [=tree/sibling=] `::view-transition-old(ident)`.

## View Transition Pseudo-elements ## {#view-transition-pseudos}

	The visualization of a [=view transition=]
	is represented as a [=pseudo-element tree=]
	called the <dfn>view transition tree</dfn>
	composed of the <dfn>view transition pseudo-elements</dfn> defined below.
	This tree is built during the [=setup transition pseudo-elements=] step,
	and is rooted under a ''::view-transition'' pseudo-element
	[=originating element|originating=] from the {{ViewTransition}}'s [=ViewTransition/root element=].
	All of the [=view transition pseudo-elements=] are selected
	from their [=ultimate originating element=],
	the {{ViewTransition}}'s [=ViewTransition/root element=].

	The [=view transition tree=] is not exposed to the accessibility tree.

	<div class="example">
		For example,
		even though a ''::view-transition-group()'' pseudo-element
		is nested underneath its parent ''::view-transition'' pseudo-element
		for styling and layout purposes,
		it's selected directly on the element hosting the {{ViewTransition}}:
		if you'd use ''.foo::view-transition'',
		you'll also use ''.foo::view-transition-group()''.

		Doing ''.foo::view-transition::view-transition-group()'' is incorrect,
		and won't select anything.
	</div>

	<div class=note>
		Once the user-agent has captured both the “old” and “new” states of the document,
		it creates a structure of pseudo-elements like the following:

		```
		::view-transition
		├─ ::view-transition-group(name)
		│  └─ ::view-transition-image-pair(name)
		│     ├─ ::view-transition-old(name)
		│     └─ ::view-transition-new(name)
		└─ …other groups…
		```

		Each element with a 'view-transition-name' is captured separately,
		and a ''::view-transition-group()'' is created for each unique 'view-transition-name'.

		For convenience, the [=document element=] is given the 'view-transition-name' "root" in the [[#ua-styles|user-agent style sheet]].

		Either ''::view-transition-old()'' or ''::view-transition-new()'' are absent in cases where the capture does not have an “old” or “new” state.

		Each of the pseudo-elements generated can be targeted by CSS in order to customize its appearance,
		behavior and/or add animations.
		This enables full customization of the transition.
	</div>

	<div class=note>
		When using ''view-transition-group'' property,
		an additional ''::view-transition-group-children()'' pseudo is created
		that allows for clipping of descendants and specifying relative transforms.

		```
		::view-transition
		├─ ::view-transition-group(name)
		│  ├─ ::view-transition-image-pair(name)
		│  │  ├─ ::view-transition-old(name)
		│  │  └─ ::view-transition-new(name)
		│  └─ ::view-transition-group-children(name)
		│			└─ …groups nested under 'name'…
		└─ …other groups…
		```

		''view-transition-group-children()'' element can now be customized to add, for example, clipping with
		'overflow: clip'. This would clip any ''view-transition-group()'' pseudo-elements along with their
		descendants.
	</div>

### Named View Transition Pseudo-elements ### {#named-view-transition-pseudo}

	Several of the [=view transition pseudo-elements=]
	are <dfn>named view transition pseudo-elements</dfn>,
	which are [=functional pseudo-element|functional=] [=tree-abiding pseudo-element|tree-abiding=] [=view transition pseudo-elements=]
	associated with a [=view transition name=].
	These pseudo-elements take a <<pt-name-selector>> as their argument,
	and their syntax follows the pattern:

	<pre class=prod>
		::view-transition-<var>pseudo</var>(<<pt-name-selector>>)
	</pre>

	where <<pt-name-selector>> selects a [=view transition name=],
	and has the following syntax definition:

	<pre class=prod>
		<dfn>&lt;pt-name-selector></dfn> = '*' | <<custom-ident>>
	</pre>

	A [=named view transition pseudo-element=] [=selector=] only matches
	a corresponding [=pseudo-element=]
	if its <<pt-name-selector>> matches that [=pseudo-element=]’s [=view transition name=],
	i.e. if it is either ''*'' or a matching <<custom-ident>>.

	Note: The [=view transition name=] of a [=view transition pseudo-element=]
	is set to the 'view-transition-name' that triggered its creation.

	The specificity of a [=named view transition pseudo-element=] [=selector=]
	with a <<custom-ident>> argument
	is equivalent to a [=type selector=].
	The specificity of a [=named view transition pseudo-element=] [=selector=]
	with a ''*'' argument
	is zero.

### View Transition Tree Root: the ''::view-transition'' pseudo-element ### {#view-transition-pseudo}

	The <dfn>::view-transition</dfn> [=pseudo-element=]
	is a [=tree-abiding pseudo-element=] that is also a [=pseudo-element root=].
	If its [=originating element=] is the document's [=document element=],
	its [=containing block=] is the [=snapshot containing block=].

	Note: In that case, the position of this element within the [=document element=] does not matter, as its [=containing block=] is the [=snapshot containing block=].

	An element's ''::view-transition'' pseudo-element only generates boxes when that pseudo-element has children.

	Note: This element serves as the [=tree/parent=] of all ''::view-transition-group()'' pseudo-elements.

### View Transition Named Subtree Root: the ''::view-transition-group()'' pseudo-element ### {#::view-transition-group}

	The <dfn>::view-transition-group()</dfn> [=pseudo-element=]
	is a [=named view transition pseudo-element=]
	that represents a matching named [=view transition=] capture.
	A ''::view-transition-group()'' [=pseudo-element=]
	is generated for each [=view transition name=]
	as a [=tree/child=] of the ''::view-transition'' [=pseudo-element=],
	and contains a corresponding ''::view-transition-image-pair()''.

	<div class=note>
		This element initially mirrors the size and position of the “old” element,
		or the “new” element if there isn't an “old” element.

		If there's both an “old” and “new” state,
		styles in the [=document/dynamic view transition style sheet=] animate this pseudo-element's 'width' and 'height'
		from the size of the old element's [=border box=] to that of the new element's [=border box=].

		Also the element's 'transform' is animated from the old element's screen space transform to the new element's screen space transform.

		This style is generated dynamically since the values of animated properties are determined at the time that the transition begins.
	</div>


### View Transition Image Pair Isolation: the ''::view-transition-image-pair()'' pseudo-element ### {#::view-transition-image-pair}

	The <dfn>::view-transition-image-pair()</dfn> [=pseudo-element=]
	is a [=named view transition pseudo-element=]
	that represents a pair of corresponding old/new [=view transition=] captures.
	This pseudo-element is a [=tree/child=] of the corresponding ''::view-transition-group()'' pseudo-element
	and contains
	a corresponding ''::view-transition-old()'' pseudo-element
	and/or
	a corresponding ''::view-transition-new()'' pseudo-element
	(in that order).

	<div class=note>
		This element exists to provide ''isolation: isolate'' for its children,
		and is always present as a [=tree/child=] of each ''::view-transition-group()''.
		This isolation allows the image pair to be blended with non-normal blend modes
		without affecting other visual outputs. As such, the developer would typically not
		need to add custom styles to the ''::view-transition-image-pair()'' pseudo-element.
		Instead, a typical design would involve styling the ''::view-transition-group()'',
		''::view-transition-old()'', and ''::view-transition-new()'' pseudo-elements.
	</div>

### View Transition Old State Image: the ''::view-transition-old()'' pseudo-element ### {#::view-transition-old}

	The <dfn>::view-transition-old()</dfn> [=pseudo-element=]
	is an empty [=named view transition pseudo-element=]
	that represents a visual snapshot of the “old” state as a [=replaced element=];
	it is omitted if there's no “old” state to represent.
	Each ''::view-transition-old()'' pseudo-element is a [=tree/child=]
	of the corresponding ''::view-transition-image-pair()'' pseudo-element.

	<div class=note>
		'':only-child'' can be used to match cases where this element is the only element in the ''::view-transition-image-pair()''.

		The appearance of this element can be manipulated with `object-*` properties in the same way that other replaced elements can be.
	</div>

	Note: The content and [=natural dimensions=] of the image are captured in [=capture the image=],
	and set in [=setup transition pseudo-elements=].

	Note: Additional styles in the [=document/dynamic view transition style sheet=] added to animate these pseudo-elements
	are detailed in [=setup transition pseudo-elements=] and [=update pseudo-element styles=].

### View Transition New State Image: the ''::view-transition-new()'' pseudo-element ### {#::view-transition-new}

	The <dfn>::view-transition-new()</dfn> [=pseudo-element=]
	(like the analogous ''::view-transition-old()'' pseudo-element)
	is an empty [=named view transition pseudo-element=]
	that represents a visual snapshot of the “new” state as a [=replaced element=];
	it is omitted if there's no “new” state to represent.
	Each ''::view-transition-new()'' pseudo-element is a [=tree/child=]
	of the corresponding ''::view-transition-image-pair()'' pseudo-element.

	Note: The content and [=natural dimensions=] of the image are captured in [=capture the image=],
	then set and updated in [=setup transition pseudo-elements=] and [=update pseudo-element styles=].

### View Transition Group Children: the ''::view-transition-group-children()'' pseudo-element ### {#::view-transition-group-children}

	The <dfn>::view-transition-group-children()</dfn> [=pseudo-element=]
	is created when mandated by the ''view-transition-group'' property
	on either the element itself or on its contained element.
	It serves as a container for descendant ''view-transition-group()'' pseudo-elements.
	By default, it is sized to the same size as the corresponding ''view-transition-group()'' pseudo element.
	By default, it has a transparent border that matches the size and shape of the border on the element that caused this structure to be created.

	Note: The size and border setting on this pseudo element allow the developer
	to enable clipping of the descedant pseudo elements with a simple ''overflow:
	clip'' property. The developer needs to take care if the original element has
	non-overscroll scrollbars, since these scrollbars change the clipping area
	from the default provided by this element.

# View Transition Layout # {#view-transition-rendering}

	The [=view transition pseudo-elements=] are styled, laid out, and rendered like normal elements.
	[=Global view transitions=] originate in the [=snapshot containing block=] rather than the [=initial containing block=]
	and are painted in the [=view transition layer=] above the rest of the document;
	[=scoped view transitions=] are simply rendered after and above all other children of their [=ViewTransition/root element=].

## The Snapshot Containing Block ## {#snapshot-containing-block-concept}

	The <dfn>snapshot containing block</dfn> is a rectangle
	that covers all areas of the window that could potentially display page content
	(and is therefore consistent regardless of root scrollbars or [=interactive-widget|interactive widgets=]).
	This makes it likely to be consistent for the [=document element=]'s [=captured element/old image=] and [=captured element/new element=].

	Within a [=child navigable=], the [=snapshot containing block=] is the union of the navigable's [=viewport=] with any [=scrollbar gutters=].

	<figure>
		<img src="diagrams/phone-browser.svg" width="200" height="335" alt="A diagram of a phone screen, including a top status bar, a browser URL bar, web-content area with a floating scrollbar, a virtual keyboard, and a bottom bar with an OS back button">
		<img src="diagrams/phone-browser-snapshot-root.svg" width="200" height="335" alt="The previous diagram, but highlights the area that's the 'snapshot containing block', which includes everything except the top status bar and the bottom bar with the OS back button">
		<figcaption>
			An example of the [=snapshot containing block=] on a mobile OS.
			The snapshot includes the URL bar, as this can be scrolled away.
			The keyboard is included as this appears and disappears.
			The top and bottom bars are part of the OS rather than the browser, so they're not included in the snapshot containing block.
		</figcaption>
	</figure>

	<figure>
		<img src="diagrams/desktop-browser.svg" width="132" height="79" alt="A diagram of a desktop browser window, including a tab bar, a URL bar, and a web-content area featuring both horizontal and vertical scrollbars" style="height:auto; width: 600px">
		<img src="diagrams/desktop-browser-snapshot-root.svg" width="132" height="79" alt="The previous diagram, but highlights the area that's the 'snapshot containing block', which includes the web content area and the scrollbars" style="height:auto; width: 600px">
		<figcaption>
			An example of the [=snapshot containing block=] on a desktop OS.
			This includes the scrollbars, but does not include the URL bar, as web content never appears in that area.
		</figcaption>
	</figure>

	The <dfn>snapshot containing block origin</dfn> refers to the top-left corner of the [=snapshot containing block=].

	The <dfn>snapshot containing block size</dfn> refers to the width and height of the [=snapshot containing block=] as a [=/tuple=] of two numbers.

	The [=snapshot containing block=] is considered to be an [=absolute positioning containing block=]
	and a [=fixed positioning containing block=] for ''::view-transition'' and its descendants.

## View Transition Painting Order ## {#view-transition-stacking-layer}

	This specification introduces a new stacking layer, the [=view transition layer=],
	to the end of the painting order established in
	<a href="https://www.w3.org/TR/CSS22/zindex.html">CSS2&sect;E Elaborate Description of Stacking Contexts</a>. [[!CSS2]]

	Issue: Fold this into [[CSS-POSITION-4]],
	as the interaction with [=top layers=] needs to be specified.

	For a [=global view transition=],
	the ''::view-transition'' pseudo-element generates a new stacking context,
	called the <dfn>view transition layer</dfn>,
	which paints after all other content of the document
	(including any content rendered in the [=Document/top layer=]),
	after any filters and effects that are applied to such content.
	(It is not subject to such filters or effects,
	except insofar as they affect the rendered contents
	of the ''::view-transition-old()'' and ''::view-transition-new()'' pseudo-elements.)

	Note: The intent of the feature is to be able to capture the contents of the page, which includes the top layer elements.
	In order to accomplish that, the [=view transition layer=] cannot be a part of the captured stacking contexts,
	since that results in a circular dependency.
	Therefore, the [=view transition layer=] is a sibling of all other content.

	When a {{Document}}'s [=document/active view transition=]'s [=ViewTransition/phase=] is "`animating`",
	the boxes generated by any element in that {{Document}} with [=captured in a view transition=]
	and its [=element contents=],
	except the [=tree/inclusive descendants=] of the ''::view-transition'' [=pseudo-element=] on that {{Document}}'s [=document element=],
	are not painted (as if they had ''opacity: 0'') and
	do not respond to hit-testing (as if they had ''pointer-events: none'').

	Note: Elements participating in a transition need to skip painting in their DOM location because
		their image is painted in the corresponding ''::view-transition-new()'' pseudo-element instead.
		Similarly, hit-testing is skipped because the element's DOM location does not correspond to where its contents are rendered.
		However, there is no change in how these elements are accessed by assistive technologies or the accessibility tree.

## Scoped View Transition Layout ## {#scoped-view-transition-layout}

For a [=scoped view transition=], the ''::view-transition'' pseudo-element
is a child of the [=ViewTransition/root element=] in the [=box tree=], but
with some behaviors that are different from ordinary children:

* The ''::view-transition'' pseudo-element generates a stacking context
	which paints separately from, and on top of, the painting of the
	[=ViewTransition/root element=] and its other children.

	Note: A consequence of painting separately is that properties on the root element
	that affect the way the root element is painted, such as 'opacity', do not apply
	to the ''::view-transition'' pseudo-element. However, if the root element is captured
	in a ''::view-transition-group'' for its own transition, such properties
	will be reflected in the captured image.

* If the [=ViewTransition/root element=] has a [=scrolling box=], the
	''::view-transition'' pseudo-element is not translated by the scroll position
	of the [=ViewTransition/root element=].

* If the [=ViewTransition/root element=] clips its [=overflow=], the
	''::view-transition'' pseudo-element is exempted from that clip.


# User Agent Stylesheet # {#ua-styles}

	The <dfn>global view transition user agent style sheet</dfn> is
	a [=user-agent origin=] style sheet containing the following rules:

	```css
	:root {
		view-transition-name: root;
	}

	::view-transition {
		position: absolute;
		inset: 0;
	}

	::view-transition-group(*) {
		position: absolute;
		top: 0;
		left: 0;

		animation-duration: 0.25s;
		animation-fill-mode: both;
	}

	::view-transition-image-pair(*) {
		position: absolute;
		inset: 0;
	}

	::view-transition-old(*),
	::view-transition-new(*) {
		position: absolute;
		display: block;
		inset-block-start: 0;
		inline-size: 100%;
		block-size: auto;
	}

	::view-transition-image-pair(*),
	::view-transition-old(*),
	::view-transition-new(*) {
		animation-duration: inherit;
		animation-fill-mode: inherit;
		animation-delay: inherit;
		animation-timing-function: inherit;
		animation-iteration-count: inherit;
		animation-direction: inherit;
		animation-play-state: inherit;
	}

	::view-transition-group-children(*) {
		position: absolute;
		inset: 0;
		border-style: solid;
		border-color: transparent;
	}

	/* Default cross-fade transition */
	@keyframes -ua-view-transition-fade-out {
		to { opacity: 0; }
	}
	@keyframes -ua-view-transition-fade-in {
		from { opacity: 0; }
	}

	/* Keyframes for blending when there are 2 images */
	@keyframes -ua-mix-blend-mode-plus-lighter {
		from { mix-blend-mode: plus-lighter }
		to { mix-blend-mode: plus-lighter }
	}
	```

	<details class="note">
		<summary>Explanatory Summary</summary>
		This UA style sheet does several things:
		* Lay out '':root::view-transition'' to cover the entire [=snapshot containing block=]
			so that each '':view-transition-group()'' child can lay out relative to it.
			Note: the placement of the [=snapshot containing block=] is similar to
			`position: fixed`.
		* For a [=scoped view transition=], lay out ''::view-transition'' to cover the
			view transition [=ViewTransition/root element=].
		* Give the [=/root element=] a default [=view transition name=],
			to allow it to be independently selected.
		* Reduce layout interference from the ''::view-transition-image-pair()'' pseudo-element
			so that authors can essentially treat ''::view-transition-old()'' and ''::view-transition-new()''
			as direct children of ''::view-transition-group()'' for most purposes.
		* Inherit animation timing through the tree so that by default,
			the animation timing set on a ''::view-transition-group()''
			will dictate the animation timing of all its descendants.
		* Style the element captures ''::view-transition-old()'' and ''::view-transition-new()''
			to match the size and position set on ''::view-transition-group()''
			(insofar as possible without breaking their aspect ratios)
			as it interpolates between them.
			Since the sizing of these elements depends on the mapping between logical and physical coordinates,
			[=dynamic view transition style sheet=] copies relevant styles from the DOM elements.
		* Set up a default quarter-second cross-fade animation
			for each ''::view-transition-group()''.
	</details>

	Additional styles are dynamically added to the [=user-agent origin=] during a [=view transition=]
	through the [=document/dynamic view transition style sheet=].


# Cross-document view transitions # {#cross-document-view-transitions}

## Overview ## {#cross-doc-overview}

*This section is non-normative.*

### Activation ### {#activating-cross-document-view-transition}
With same-document view transitions, the author starts a view transition using JavaScript, by calling {{Document/startViewTransition}}.
In cross-document view transition, what triggers a view transition is a navigation between two documents, as long as the following conditions are met:

	* Both documents are of the [=same origin=];
	* The page is visible throughout the entire course of the navigation;
	* The user initiates the navigation by interacting with the page, e.g. by clicking a link or submitting a form; or by interacting with the browser UI to do a {{NavigationType/traverse}} navigation (back/forward). This excludes, for example, navigations initiated by the URL bar;
	* the navigation didn't include cross-origin redirects; and
	* both documents opted in to cross-document view transitions, using the ''@view-transition'' rule.

See the <a href="#lifecycle">lifecycle</a> section for more details.

### Waiting for the new state to stabilize ### {#waiting-for-stable-state}
In same-document view transitions, the author can indicate when the new state of the transition is in a stable state by using the callback passed to {{Document/startViewTransition}}.
Since cross-document view transitions are declarative, there is no such explicit promise. Instead, the user agent relies on the [=render-blocking mechanism=] to decide when the document has reached a stable state.
In this way, the author can use the <code data-x="">blocking</code> attribute, to delay the transition until:
	* All expected scripts are executed, by using the script's {{HTMLScriptElement/blocking}} attribute on required scripts.
	* All expected styles are executed, by using the style or link's {{HTMLStyleElement/blocking}} attribute on required styles.
	* All expected HTML elements are seen by the parser, using an <{link/rel/expect}> {{HTMLLinkElement}} element.

Note: overusing the render-blocking mechanism could make it so that the old state remains frozen for a long time, resulting in a jarring user experience.
To avoid this, it's advised to ensure that the render-blocking elements are available in a timely manner.

<div class="example">

In this example, the last frame of the old document will be shown, and the animation will be delayed, until all the following conditions are met:
- `style.css` is applied, to ensure the new state has the correct styles
- `fixup.js` is run, to ensure the presentation is up to date with script-based fixups.
- The `main-article` section is seen and parsed, to ensure enough of the content is loaded before allowing the transition to proceed.

```html
<!DOCTYPE html>
<html>
	<head>
		< !-- This will be render-blocking by default -->
		<link rel="stylesheet" href="style.css">

		< !-- Since this script fixes up the layout, marking it as render blocking will
			  ensure it's run before the view transition is activated -->
		<script async href="fixup.js" blocking="render"></script>

		< !-- Wait until the main-article element is seen and fully parsed before
			  activating the transition -->
		<link rel="expect" href="#main-article" blocking="render">
	</head>
	<body>
		<header>...</header>
		<main>
			<article id="main-article">...</article>
		</main>
		<article id="secondary-article">...</article>
	</body>
</html>
```
</div>

### Customization ### {#customizing-cross-document-view-transitions}
The {{ViewTransition}} object enables customizing the transition in script.
Same-document view transitions use a single {{ViewTransition}} object returned from the {{Document/startViewTransition}} call for the entire lifecycle.
Cross-document view transitions have two {{ViewTransition}} objects, one in the old document and one in the new document.

#### Handling the view transition in the old document #### {#old-doc-event}

The {{Window/pageswap}} event is fired at the last moment before a document is about to be unloaded and swapped by another document.
It can be used to find out whether a view transition is about to take place, customize it using {{ViewTransition/types}}, make last minute changes to the captured elements, or skip it if necessary.
The {{PageSwapEvent}} interface has a {{PageSwapEvent/viewTransition}} object, which would be non-null when the navigation is eligible to a view transition,
and a {{PageSwapEvent/activation}} object, providing handy information about the navigation, like the URL after redirects.
The transition's {{ViewTransition/finished}} promise can be used for cleaning up after the transition, in case the document is later restored from BFCache.

#### Handling the view transition in the new document #### {#new-doc-event}

The {{Window/pagereveal}} event is fired right before presenting the first frame of the new document.
It can be used to find out if the view transition is still valid, by querying the {{PageRevealEvent/viewTransition}} attribute.
Similar to a same-document view transition, the author can now select different {{ViewTransition/types}}, make last minute changes to the captured elements, wait for the transition to be {{ViewTransition/ready}} in order to animate it, or skip it altogether.

### Lifecycle ### {#cross-doc-lifecycle}

	*This section is non-normative.*

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

	1. In the old {{Document}}:

		1. The user initiates a navigation, by clicking a link, submitting a form, pressing the browser's back button, etc.

			Note: some navigations do not trigger a view-transition, e.g. typing a new address in the URL bar.

		1. When the new {{Document}} is ready to be activated, the {{Window/pageswap}} event is fired.

		1. If the navigation is [=same origin=], has no cross-origin redirects,
			and the old {{Document}} has <a href="#cross-doc-opt-in">opted in to cross-document view transitions</a>, the event's {{PageSwapEvent/viewTransition}} attribute would be
			a {{ViewTransition}} object.

		1. The author can now customize the transition, e.g. by mutating its {{ViewTransition/types}}, or {{ViewTransition/skipTransition()|skip}} it altogether.

		1. If the {{ViewTransition}} is not skipped, the state of the old document is [=capture the old state|captured=].

		1. The navigation proceeds: the old {{Document}} is unloaded, and the new {{Document}} is now active.

	1. Then, in the new {{Document}}:

		1. When the new {{Document}} is ready for its first [=rendering opportunity=], an event named {{Window/pagereveal}} is fired on the new {{Document}}, with a {{PageRevealEvent/viewTransition}} attribute.

		1. This {{ViewTransition}}'s <code>{{ViewTransition/updateCallbackDone}}</code> promise is already resolved,
			and its [=captured elements=] are populated from the old {{Document}}.

		1. This is another opportunity for the author to customize the transition, e.g. by mutating its {{ViewTransition/types}}, or {{ViewTransition/skipTransition()|skip}} it altogether.

		1. The state of the new document is [=capture the new state|captured=] as the "new" state of the transition.

		1. From this point forward, the transition continues in a similar fashion to a same-document transition, as per [=activate view transition=].

## Examples ## {#cross-doc-examples}

<div class=example>
	To generate the same cross-fade as in the first example [[css-view-transitions-1#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:

	```css
	// 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.
</div>

<div class=example>
	Note that the ''@view-transition'' rule can be used together with media queries.
	For example, this would only perform the transition when the screen width is greater than:

	```css
	@view-transition {
		navigation: auto;
	}

	@media (max-width: 600px) {
		navigation: none;
	}
	```
</div>

<div class="example">
	To achieve the effect in [[css-view-transitions-1#examples|example 5]], we have to do several
	things:

	- Opt-in to navigation-triggered view-transitions in both pages.
	- Pass the click location to the new document, e.g. via {{WindowSessionStorage/sessionStorage}}.
	- Intercept the {{ViewTransition}} object in the new document, using the {{Window/pagereveal}} event.

	In both pages:
	```css
	@view-transition {
		navigation: auto;
	}

	```
	In the old page:
	```js
	addEventListener('click', event => {
		sessionStorage.setItem("lastClickX", event.clientX);
		sessionStorage.setItem("lastClickY", event.clientY);
	});
	```

	In the new page:
	```js
	// 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)'
			}
		);
	})
	```
</div>

<div class="example">
	To choose which elements are captured based on properties of the navigation, and whether certain images are loaded:

	In the old page:
	```js
		window.addEventListener("pageswap", event => {
			// For example, the page was hidden, or the navigation is cross-document.
			if (!event.viewTransition)
				return;

			// If you don't want view transition for back/forward navigations...
			if (event.activation.navigationType === "traverse") {
				event.viewTransition.skipTransition();
			}

			const newURL = new URL(event.activation.entry.url);
			if (newURL.pathname === "/details" && thumbnail.complete) {
				thumbnail.classList.add("transition-to-hero");

				// This will cleanup the state if the page is restored from BFCache.
				event.viewTransition.finished.then(() => {
					thumbnail.classList.remove("transition-to-hero");
				});
			}

		});
	```

	In the new page:
	```js
		window.addEventListener("pagereveal", event => {
			// For example, the page was hidden, the navigation is cross-document, or the transition was skipped in the old document.
			if (!event.viewTransition)
				return;

			const oldURL = new URL(navigation.activation.from.url);
			if (newURL.pathname === "/list") {
				event.viewTransition.types.add("coming-from-list");

				// This will show the thumbnail until the view transition is finished.
				if (!hero.complete) {
					setToThumbnail(hero);
					event.viewTransition.finished.then(() => {
						setToFullResolution(hero);
					})
				}
			}
		});
	```
</div>

## Opting in to cross-document view transitions ## {#cross-doc-opt-in}

### The <dfn id="at-view-transition-rule">''@view-transition''</dfn> rule ### {#view-transition-rule}

The ''@view-transition'' rule is used by a document to indicate that cross-document navigations
should setup and activate a {{ViewTransition}}.

The ''@view-transition'' rule has the following syntax:

<pre class=prod>
	@view-transition {
		<<declaration-list>>
	}
</pre>

The ''@view-transition'' rule accepts the [=@view-transition/navigation=] and [=@view-transition/types=] descriptors.

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

When the ''@view-transition'' rule changes for {{Document}} |document|, the user agent must [=update the opt-in state for outbound transitions=] given |document|.

Note: this needs to be cached in the boolean because the result needs to be read [=in parallel=], when navigating.

### The [=@view-transition/navigation=] descriptor ### {#view-transition-navigation-descriptor}

	<pre class='descdef'>
	Name: navigation
	For: @view-transition
	Value: auto | none
	Initial: none
	</pre>

	The '<dfn for="@view-transition">navigation</dfn>' descriptor opts in to automatically starting a view transition when performing a navigation of a certain type.
	Must be present on both the old and new document.

	<dl dfn-type=value dfn-for="@view-transition/navigation">
		: <dfn>none</dfn>
		:: There will be no transition.

		: <dfn>auto</dfn>
		:: The transition will be enabled if the navigation is same-origin, without cross-origin
			redirects, and whose {{NavigationType}} is
			* {{NavigationType/traverse}}, or
			* {{NavigationType/push}} or {{NavigationType/replace}}, with <a href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#user-navigation-involvement">user navigation involvement</a> not equal to `"browser UI"`.

			Note: Navigations excluded from ''@view-transition/navigation/auto'' are for example, navigating
			via the URL address bar or clicking a bookmark, as well as any form of user or script initiated {{NavigationType/reload}}.
	</dl>

This at-rule conforms with the forward-compatible parsing requirement of CSS;
conformant parsers that don't understand these rules will ignore them without error.
Any descriptors that are not recognized or implemented by a given user agent,
or whose value does not match the grammars given here or in a future version of this specification,
must be ignored in their entirety;
they do not make the ''@view-transition'' rule invalid.

### Accessing the ''@view-transition'' rule using CSSOM ### {#cssom}

The {{CSSViewTransitionRule}} represents a ''@view-transition'' rule.

<xmp class=idl>
		[Exposed=Window]
		interface CSSViewTransitionRule : CSSRule {
			readonly attribute CSSOMString navigation;
			[SameObject] readonly attribute FrozenArray<CSSOMString> types;
		};
</xmp>

The {{CSSViewTransitionRule/navigation}} getter step is to return the value of the corresponding ''@view-transition/navigation'' descriptor if one exists, otherwise the empty string.

The {{CSSViewTransitionRule/types}} getter steps is to return the value of the corresponding ''@view-transition/types'' descriptor if one exists, otherwise an empty [=list=].

# Selective view transitions # {#selective-vt}

## Overview ## {#selective-vt-overview}

*This section is non-normative.*

For simple pages, with a single view transition, setting the ''view-transition-name'' property on participating elements should be sufficient.
However, in more complex scenarios, the author might want to declare various view transitions, and only run one of them simultaneously.
For example, sliding the whole page when clicking on the navigation bar, and sorting a list when one of its items is dragged.

To make sure each view transition only captures what it needs to, and different transitions don't interfere with each other,
this spec introduces the concept of [=active types=], alongside the '':active-view-transition'' and '':active-view-transition-type()'' pseudo-classes.

'':active-view-transition'' matches the [=document element=] when it has an [=active view transition=], and '':active-view-transition-type()''
matches the [=document element=] if the types in the selectors match the [=active view transition=]'s [=ViewTransition/active types=].

The {{ViewTransition}}'s [=active types=] are populated in one of the following ways:

	1. Passed as part of the arguments to {{Document/startViewTransition(callbackOptions)}}
	1. Mutated at any time, using the transition's {{ViewTransition/types}}
	1. Declared for a cross-document view transition, using the [=@view-transition/types=] descriptor.

## Examples ## {#active-view-transition-pseudo-examples}

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

This will activate any of the following selectors:
```css
:root:active-view-transition-type(slide-in) {}
:root:active-view-transition-type(reverse) {}
:root:active-view-transition-type(slide-in, reverse) {}
:root:active-view-transition-type(slide-in, something-else) {}
:root:active-view-transition {}
```

While starting a transition without providing transition types, would only activate '':active-view-transition'':

```js
document.startViewTransition(updateTheDOMSomehow);
// or
document.startViewTransition({update: updateTheDOMSomehow});
```

```css
/* This would be active */
:root { }
:root:active-view-transition {}

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

## Selecting based on the active view transition ## {#pseudo-classes-for-selective-vt}
### The '':active-view-transition'' pseudo-class ### {#the-active-view-transition-pseudo}

The <dfn id='active-view-transition-pseudo'>:active-view-transition</dfn> pseudo-class applies to the root element of the document, if it has an [=active view transition=].

The [=specificity=] of an '':active-view-transition'' is one pseudo-class selector.

An '':active-view-transition'' pseudo-class matches the [=document element=] when its [=node document=] has an non-null [=active view transition=].

### The '':active-view-transition-type()'' pseudo-class ### {#the-active-view-transition-type-pseudo}

The <dfn id="active-view-transition-type-pseudo">:active-view-transition-type()</dfn> pseudo-class applies to the root element of the document, if it has a matching [=active view transition=].
It has the following syntax definition:

<pre class=prod>
	:active-view-transition-type(<<custom-ident>>#)
</pre>

The [=specificity=] of an '':active-view-transition-type()'' is one pseudo-class selector.

An '':active-view-transition-type()'' pseudo-class matches the [=document element=] when its [=node document=] has an non-null [=active view transition=],
whose [=ViewTransition/active types=] [=list/contains=] at least one of the <<custom-ident>> arguments.


## Activating the transition type for cross-document view transitions ## {#types-cross-doc}

The [=@view-transition/types=] descriptor

	<pre class='descdef'>
	Name: types
	For: @view-transition
	Value: none | <<view-transition-type>>+
	Initial: none
	</pre>

	The '<dfn for="@view-transition">types</dfn>' descriptor sets the [=ViewTransition/active types=] for the transition
	when capturing or performing the transition, equivalent to calling {{Document/startViewTransition(callbackOptions)}} with that {{StartViewTransitionOptions/types}}.

	<dl dfn-type=value dfn-for="@view-transition/types">
		: <dfn>none</dfn>
		:: No transition types are set.

		: <dfn><<view-transition-type>>+</dfn>
		:: Sets the [=ViewTransition/active types=] for the transition to the specified custom identifiers.
	</dl>

	where <dfn>&lt;view-transition-type></dfn> is any <<custom-ident>> other than `none`, and not starting with the string "-ua-".

	Note: the [=@view-transition/types=] descriptor only applies to the {{Document}} in which it is defined.
	The author is responsible for using their chosen set of types in both documents.

# Sharing styles between view transition pseudo-elements # {#shared-style-with-vt-classes}

## Overview ## {#shared-style-overview}

*This section is non-normative.*

When styling multiple elements in the DOM in a similar way, it is common to use the [=Element/class=] attribute:
setting a name that's shared across multiple elements, and then using the [=class selector=] to declare the shared style.

[=Named view transition pseudo-elements|The view transition pseudo-elements=] (e.g. ''view-transition-group()'') are not defined in the DOM, but rather by using the ''view-transition-name'' property.
For that purpose, the ''view-transition-class''' CSS property provides view transitions with the equivalent of HTML [=Element/class|classes=].
When an element with a ''view-transition-name'' also has a ''view-transition-class'' value, that class would be selectable by the pseudo-elements, as per the <a href="#vt-class-example">examples</a>.

## Examples ## {#vt-class-example}

<div class="example">
This example creates a transition with each box participating under its own name, while applying
a 1-second duration to the animation of all the boxes:

```html
<div class="box" id="red-box"></div>
<div class="box" id="green-box"></div>
<div class="box" id="yellow-box"></div>
```

```css
div.box {
	view-transition-class: any-box;
	width: 100px;
	height: 100px;
}
#red-box {
	view-transition-name: red-box;
	background: red;
}
#green-box {
	view-transition-name: green-box;
	background: green;
}
#yellow-box {
	view-transition-name: yellow-box;
	background: yellow;
}

/* The following style would apply to all the boxes, thanks to 'view-transition-class' */
::view-transition-group(*.any-box) {
	animation-duration: 1s;
}
```
</div>

## The 'view-transition-class' property ## {#view-transition-class-prop}

	<pre class=propdef>
	Name: view-transition-class
	Value: none | <<custom-ident>>+
	Initial: none
	Inherited: no
	Percentages: n/a
	Computed Value: as specified
	Animation type: discrete
	</pre>

	The 'view-transition-class' can be used to apply the same style rule to multiple [=named view transition pseudo-elements=] which may have a different 'view-transition-name'.
	While 'view-transition-name' is used to match between the element in the old state with its corresponding element in the new state, 'view-transition-class' is used
	only to apply styles using the view transition pseudo-elements
	(''::view-transition-group()'', ''::view-transition-image-pair()'', ''::view-transition-old()'', ''::view-transition-new()'').

	Note that 'view-transition-class' by itself doesn't mark an element for capturing, it is only used as an additional
	way to style an element that already has a 'view-transition-name'.

	<dl dfn-type=value dfn-for=view-transition-class>
		: <dfn>none</dfn>
		:: No class would apply to the [=named view transition pseudo-elements=] generated for this element.

		: <dfn><<custom-ident>>+</dfn>
		:: All of the specified <<custom-ident>> values (apart from <css>none</css>) are applied when used in [=named view transition pseudo-element=] selectors.
			<css>none</css> is an invalid <<custom-ident>> for 'view-transition-class', even when combined with another <<custom-ident>>.

			Each 'view transition class' is a [=tree-scoped name=].

			Note: If the same 'view-transition-name' is specified for an element both in the old and new states of the transition,
			only the 'view-transition-class' values from the new state apply. This also applies for cross-document view transitions:
			classes from the old document would only apply if their corresponding 'view-transition-name' was not specified in the new document.
	</dl>

## Additions to named view transition pseudo-elements ## {#pseudo-element-class-additions}

	The [=named view transition pseudo-elements=]
	(''view-transition-group()'', ''view-transition-image-pair()'', ''view-transition-old()'', ''view-transition-new()'')
	are extended to support the following syntax:

	<pre class=prod>
		::view-transition-group(<<pt-name-and-class-selector>>)
		::view-transition-image-pair(<<pt-name-and-class-selector>>)
		::view-transition-old(<<pt-name-and-class-selector>>)
		::view-transition-new(<<pt-name-and-class-selector>>)
	</pre>

	where <<pt-name-selector>> works as previously defined, and
	<<pt-name-and-class-selector>> has the following syntax definition:

	<pre class=prod>
		<dfn>&lt;pt-name-and-class-selector></dfn> = <<pt-name-selector>> <<pt-class-selector>>? | <<pt-class-selector>>
		<dfn>&lt;pt-class-selector></dfn> = ['.' <<custom-ident>>]+
	</pre>

	When interpreting the above grammar, white space is forbidden:

		* Between <<pt-name-selector>> and <<pt-class-selector>>
		* Between any of the components of <<pt-class-selector>>.

	A [=named view transition pseudo-element=] [=selector=] which has one or more <<custom-ident>> values
	in its <<pt-class-selector>> would only match an element if the [=captured element/class list=] value in
	[=ViewTransition/named elements=] for the pseudo-element's 'view-transition-name' [=list/contains=] all of those values.

	The [=specificity=] of a [=named view transition pseudo-element=] [=selector=] with either:
		* a <<pt-name-selector>> with a <<custom-ident>>; or
		* a <<pt-class-selector>> with at least one <<custom-ident>>,

	is equivalent to a [=type selector=].

	The [=specificity=] of a [=named view transition pseudo-element=] [=selector=]
	with a ''*'' argument and with an empty <<pt-class-selector>> is zero.





# Determining 'view-transition-name' automatically # {#auto-vt-name}

## Overview ## {#auto-vt-name-overview}

*This section is non-normative.*

For an element to participate in a view transition, it needs to receive a unique 'view-transition-name'.
This can be tedious and verbose when multiple elements are involved in the same view transition, especially in cases where many of those are same-element transitions,
as in, the element has the same 'view-transition-name' in the old and new state.

To make this easier, setting the 'view-transition-name' to ''view-transition-name/auto'' would generate a 'view-transition-name' for the element, or take it from the element's [=Element/id=] if present.

## Examples ## {#auto-vt-name-example}

<div class="example">
In this example, view transition is used to sort a list in an animated way:

```html
<ul>
	<li>Item 1</li>
	<li>Item 2</li>
	<li>Item 3</li>
	<li>Item 4</li>
	...
</ul>
```

Ordinarily, each of these items would have to receive a unique 'view-transition-name':

```css
li:nth-child(1) { view-transition-name: item1; }
li:nth-child(2) { view-transition-name: item2; }
li:nth-child(3) { view-transition-name: item3; }
li:nth-child(4) { view-transition-name: item4; }
...
```

With ''view-transition-name/auto'' or ''view-transition-name/match-element'', this CSS would work:

```css
li {
	view-transition-name: auto;
	/* or */
	view-transition-name: match-element;
}
```

The ''view-transition-name/auto'' and ''view-transition-name/match-element'' keywords have a minor
difference, where ''view-transition-name/auto'' would use the element's [=Element/id=] as the 'view-transition-name'
if it exists, making it potentially work across documents, while ''view-transition-name/match-element'' only matches
based on element identity.
</div>

## Additions to 'view-transition-name' ## {#additions-to-vt-name}

In addition to the existing values, the 'view-transition-name' also accepts an <dfn for=view-transition-name export>auto</dfn> keyword.
To resolve the [=used value=] of 'view-transition-name' for |element|:
	1. Let |computed| be the [=computed value=] of 'view-transition-name'.
	1. If |computed| is <css>none</css>, return null.
	1. If |computed| is a <<custom-ident>>, return |computed|.
	1. Assert: |computed| is <css>auto</css> or <css>match-element</css>.
	1. If |computed| is <css>auto</css>, |element| has an associated [=Element/id=], and |computed| is associated with the same [=tree/root=] as |element|'s [=tree/root=], then return a unique string starting with "<code>-ua-</code>".
		Two elements with the same [=Element/id=] must return the same string, regardless of their [=node document=].

		Note: this means that a ''::part()'' pseudo-element wouldn't resolve to its matching element's [=Element/id=].

	1. Return a unique string starting with "<code>-ua-</code>". The string should remain consistent and unique for this element and {{Document}}, at least for the lifetime of |element|'s [=node document=]'s [=active view transition=].

		Note: When used in a cross-document view transition, generated <css>auto</css> values never match, resulting in separate ''::view-transition-group()'' pseudo-elements, one exiting and one entering.

A 'view-transition-name' generated by <css>auto</css> is a [=tree-scoped name=].

# Algorithms # {#algorithms}

## Data Structures ## {#concepts}

### Additions to {{Document}} ### {#additions-to-document}

	A {{Document}} additionally has:

	<dl dfn-for=document export>
		: <dfn>active view transition</dfn>
		:: a {{ViewTransition}} or null. Initially null.

		: <dfn>rendering suppression for view transitions</dfn>
		:: a boolean. Initially false.

			While a {{Document}}’s [=document/rendering suppression for view transitions=] is true,
			all pointer hit testing must target its [=document element=],
			ignoring all other [=elements=].

			Note: This does not affect pointers that are [=pointer capture|captured=].

		: <dfn>dynamic view transition style sheet</dfn>
		:: a [=/style sheet=].
			Initially a new [=/style sheet=] in the [=user-agent origin=], ordered after the [=global view transition user agent style sheet=].

			Note: This is used to hold dynamic styles relating to transitions.

		: <dfn>update callback queue</dfn>
		:: A [=/list=], initially empty.

		: <dfn>inbound view transition params</dfn>
		:: a [=view transition params=], or null.
			Initially null.

		: <dfn>can initiate outbound view transition</dfn>
		:: a boolean.
			Initially false.

			Note: this value can be read [=in parallel=] while navigating.

		: <dfn>wait until promise count</dfn>
		:: an integer, initially 0.

			Represents the count of promises that have been added via {{ViewTransition/waitUntil}} function
			and have not settled.
	</dl>

### Additions to Elements ### {#elements-concept}

	[=/Elements=] have a <dfn export>captured in a view transition</dfn> boolean, initially false.

	Note: This spec uses CSS's definition of [=element=], which includes [=pseudo-elements=].

### [=Captured elements=] ### {#captured-elements}

	A <dfn>captured element</dfn> is a [=struct=] with the following:

	<dl dfn-for="captured element">
		: <dfn>old image</dfn>
		:: an 2D bitmap or null. Initially null.

		: <dfn>old width</dfn>
		: <dfn>old height</dfn>
		:: an {{unrestricted double}}, initially zero.

		: <dfn>old transform</dfn>
		:: a <a data-xref-type="css-type">&lt;transform-function&gt;</a>, initially the [=identity transform function=].

		: <dfn>old writing-mode</dfn>
		:: Null or a 'writing-mode', initially null.

		: <dfn>old direction</dfn>
		:: Null or a 'direction', initially null.

		: <dfn>old text-orientation</dfn>
		:: Null or a 'text-orientation', initially null.

		: <dfn>old mix-blend-mode</dfn>
		:: Null or a 'mix-blend-mode', initially null.

		: <dfn>old backdrop-filter</dfn>
		:: Null or a 'backdrop-filter', initially null.

		: <dfn>old color-scheme</dfn>
		:: Null or a 'color-scheme', initially null.

		: <dfn>new element</dfn>
		:: an [=/element=] or null. Initially null.

		: <dfn for="captured element">class list</dfn>
		:: a [=/list=] of strings, initially empty.

		: <dfn for="captured element">containing group name</dfn>
		:: Null or a string, initially null.

		: <dfn for="captured element">transform from snapshot containing block</dfn>
		:: A [=matrix=] or null, initially null.

		: <dfn for="captured element">old border width</dfn>
		:: 'border-width' [=used value=] (top, right, bottom and left pixel values) or null, initially null.

		: <dfn for="captured element">duplicate</dfn>
		:: A boolean, initiallly false.
	</dl>

	In addition, a [=captured element=] has the following <dfn for="captured element">style definitions</dfn>:

	<dl dfn-for="captured element">
		: <dfn>group keyframes</dfn>
		:: A {{CSSKeyframesRule}} or null. Initially null.

		: <dfn>group animation name rule</dfn>
		:: A {{CSSStyleRule}} or null. Initially null.

		: <dfn>group styles rule</dfn>
		:: A {{CSSStyleRule}} or null. Initially null.

		: <dfn>image pair isolation rule</dfn>
		:: A {{CSSStyleRule}} or null. Initially null.

		: <dfn>image animation name rule</dfn>
		:: A {{CSSStyleRule}} or null. Initially null.
	</dl>

	Note: These are used to update, and later remove styles
	from a [=/document=]'s [=document/dynamic view transition style sheet=].

### Serializable [=view transition params=] ### {#cross-doc-data-structure-serialization}
A <dfn>view transition params</dfn> is a [=struct=] whose purpose is to serialize view transition information across documents.
It has the following [=struct/items=]:

<dl dfn-for="view transition params">
	: <dfn>named elements</dfn>
	:: a [=/map=], whose keys are strings and whose values are [=captured elements=].

	: <dfn>initial snapshot containing block size</dfn>
	:: a [=tuple=] of two numbers (width and height).
</dl>


## [=Perform pending transition operations=] ## {#perform-pending-transition-operations-algorithm}

	<div algorithm>
		<div class=note>This algorithm is invoked as a part of <a href="https://html.spec.whatwg.org/#event-loop-processing-model:perform-pending-transition-operations">update the rendering loop</a> in the html spec.</div>

		To <dfn>perform pending transition operations</dfn> given a {{Document}} |document|, perform the following steps:

		1. If |document|'s [=document/active view transition=] is not null, then:

			1. If |document|'s [=document/active view transition=]'s [=ViewTransition/phase=] is "`pending-capture`",
				then [=setup view transition=] for |document|'s [=document/active view transition=].

			1. Otherwise, if |document|'s [=document/active view transition=]'s [=ViewTransition/phase=] is "`animating`",
				then [=handle transition frame=] for |document|'s [=document/active view transition=].
	</div>

## Resolving the ''@view-transition'' rule ## {#vt-rule-algo}
	<div algorithm>
		To <dfn>resolve @view-transition rule</dfn> for a {{Document}} |document|:

		Note: this is called in both the old and new document.

		1. If |document|'s [=Document/visibility state=] is "<code>hidden</code>",
			then return "<code>skip transition</code>".

		1. Let |matchingRule| be the last ''@view-transition'' rule in |document|.

		1. If |matchingRule| is not found, then return "<code>skip transition</code>".

		1. If |matchingRule|'s [=@view-transition/navigation=] descriptor's [=computed value=] is ''@view-transition/navigation/none'', then return "<code>skip transition</code>".

		1. Assert: |matchingRule|'s [=@view-transition/navigation=] descriptor's [=computed value=] is ''@view-transition/navigation/auto''.

		1. Let |typesDescriptor| be |matchingRule|'s [=@view-transition/types=] descriptor.

		1. If |typesDescriptor|'s [=computed value=] is ''@view-transition/type/none'', then return a [=/set=] « ».

		1. Return a [=/set=] of strings corresponding to |typesDescriptor|'s [=computed value=].
	</div>

## [=Setup view transition|Setup single-document view transition=] ## {#setup-view-transition-algorithm}

	<div algorithm>
		To <dfn>setup view transition</dfn> for a {{ViewTransition}} |transition|,
			perform the following steps:

		Note: This algorithm captures the current state of the document,
		calls the transition's {{ViewTransitionUpdateCallback}},
		then captures the new state of the document.

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. [=Flush the update callback queue=].

			Note: this ensures that any changes to the DOM scheduled by other skipped transitions are done before the old state for this transition is captured.

		1. [=Capture the old state=] for |transition|.

			If failure is returned, then [=skip the view transition=] for |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
			and return.

		1. Set |document|'s [=document/rendering suppression for view transitions=] to true.

		1. [=Queue a global task=] on the [=DOM manipulation task source=],
			given |transition|'s [=relevant global object=],
			to perform the following steps:

				Note: A task is queued here because the texture read back in [=capturing the image=] may be async,
					although the render steps in the HTML spec act as if it's synchronous.

			1. If |transition|'s [=ViewTransition/phase=] is "`done`", then abort these steps.

				Note: This happens if |transition| was [=skip the view transition|skipped=] before this point.

			1. [=schedule the update callback=] for |transition|.

			1. [=Flush the update callback queue=].
	</div>

	<div algorithm>
		To <dfn>activate view transition</dfn> for a {{ViewTransition}} |transition|,
			perform the following steps:

		1. If |transition|'s [=ViewTransition/phase=] is "`done`", then return.

			Note: This happens if |transition| was [=skip the view transition|skipped=] before this point.

		1. Set |transition|'s [=relevant global object's=] [=associated document=]'s [=document/rendering suppression for view transitions=] to false.

		1. If |transition|'s [=ViewTransition/initial snapshot containing block size=] is not equal to the [=snapshot containing block size=],
			then [=skip the view transition|skip=] |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
			and return.

		1. [=Capture the new state=] for |transition|.

			If failure is returned, then [=skip the view transition|skip=] |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
			and return.

		1. [=list/Remove=] from |transition|'s [=ViewTransition/named elements=] every entry whose value's [=captured element/duplicate=] is true.

			Note: User agents are encouraged to alert the developer that the duplicate was removed, e.g. via a console warning.

		1. [=list/For each=] |capturedElement| of |transition|'s [=ViewTransition/named elements=]' [=map/values=]:

			1. If |capturedElement|'s [=captured element/new element=] is not null,
				then set |capturedElement|'s [=captured element/new element=]'s [=captured in a view transition=] to true.

		1. [=Setup transition pseudo-elements=] for |transition|.

		1. [=Update pseudo-element styles=] for |transition|.

			If failure is returned, then [=skip the view transition=] for |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
			and return.

			Note: The above steps will require running document lifecycle phases,
				to compute information calculated during style/layout.

		1. Set |transition|'s [=ViewTransition/phase=] to "`animating`".

		1. [=Resolve=] |transition|'s [=ViewTransition/ready promise=].
	</div>

### [=Capture the old state=] ### {#capture-old-state-algorithm}

	<div algorithm>
		To <dfn>capture the old state</dfn> for {{ViewTransition}} |transition|:

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. Let |namedElements| be |transition|'s [=ViewTransition/named elements=].

		1. Let |captureElements| be a new [=/list=] of elements.

		1. If the [=snapshot containing block size=] exceeds an [=implementation-defined=] maximum, then return failure.

		1. Set |transition|'s [=ViewTransition/initial snapshot containing block size=] to the [=snapshot containing block size=].

		1. [=list/For each=] |element| of every [=/element=] that is [=/connected=],
			and has a [=node document=] equal to |document|,
			in [paint order](https://drafts.csswg.org/css2/#painting-order):

			Note: We iterate in paint order to ensure that this order is cached in |namedElements|.
			This defines the DOM order for ::view-transition-group pseudo-elements, such that the element at the bottom of the paint stack generates the first pseudo child of ::view-transition.

			1. If any [=flat tree=] ancestor of this |element| [=skips its contents=], then [=continue=].

			1. If any [=flat tree=] [=inclusive ancestor=] of this |element|,
				up to but not including |transition|'s [=ViewTransition/root element=],
				has ''view-transition-scope: all'',
				[=continue=].

			1. If |element| has more than one [=box fragment=], then [=continue=].

				Note: We might want to enable transitions for fragmented elements in future versions.
				See [#8900](https://github.com/w3c/csswg-drafts/issues/8900).

				Note: [=box fragment=] here does not refer to fragmentation of <a href="https://www.w3.org/TR/CSS2/visuren.html#inline-boxes">inline boxes</a> across <a href="https://www.w3.org/TR/CSS2/visuren.html#line-box">line boxes</a>.
					Such inlines can participate in a transition.

			1. Let |transitionName| be the |element|'s [=document-scoped view transition name=].

			1. If |transitionName| is ''view-transition-name/none'',
				or |element| is [=element-not-rendered|not rendered=],
				then [=continue=].

			1. If |namedElements|[|transitionName|] exists, then set |namedElements|[|transitionName|]'s [=captured element/duplicate=] to true and [=continue=].

			1. If <em>any other</em> [=active view transition=] contains |element|
				in its [=captured elements=],
				then for each such [=active view transition=],
				in [=tree order=] of their corresponding [=ViewTransition/root elements=],
				[=skip the view transition|skip=] that view transition with an "{{AbortError}}" {{DOMException}} in |document|’s [=relevant Realm=].

			1. Set |element|'s [=captured in a view transition=] to true.

			1. [=list/Append=] |element| to |captureElements|.

			<div class="note">The algorithm continues in a separate loop to ensure that [=captured in a view transition=] is set on all elements participating in this capture before it is read by future steps in the algorithm.</div>

		1. [=list/For each=] |element| in |captureElements|:

			1. Let |capture| be a new [=captured element=] struct.

			1. Set |capture|'s [=old image=] to the result of [=capturing the image=] of |element|.

			1. Let |originalRect| be [=snapshot containing block=] if |element| is the [=document element=],
				otherwise, the |element|'s [=border box=].

			1. Set |capture|'s [=captured element/old width=] to |originalRect|'s {{DOMRect/width}}.

			1. Set |capture|'s [=captured element/old height=] to |originalRect|'s {{DOMRect/height}}.

			1. Set |capture|'s [=captured element/old transform=] to a
				<a data-xref-type="css-type">&lt;transform-function&gt;</a> that would map
				|element|'s [=border box=] from the [=snapshot containing block origin=] to its
				current visual position.

			1. Set |capture|'s [=captured element/old writing-mode=] to the [=computed value=] of 'writing-mode' on |element|.

			1. Set |capture|'s [=captured element/old direction=] to the [=computed value=] of 'direction' on |element|.

			1. Set |capture|'s [=captured element/old text-orientation=] to the [=computed value=] of 'text-orientation' on |element|.

			1. Set |capture|'s [=captured element/old mix-blend-mode=] to the [=computed value=] of 'mix-blend-mode' on |element|.

			1. Set |capture|'s [=captured element/old backdrop-filter=] to the [=computed value=] of 'backdrop-filter' on |element|.

			1. Set |capture|'s [=captured element/old color-scheme=] to the [=computed value=] of 'color-scheme' on |element|.

			1. Let |transitionName| be the [=computed value=] of 'view-transition-name' for |element|.

			1. Set |namedElements|[|transitionName|] to |capture|.

		1. [=list/For each=] |element| in |captureElements|:

			1. Set |element|'s [=captured in a view transition=] to false.
	</div>

### [=Capture the new state=] ### {#capture-new-state-algorithm}

	<div algorithm>
		To <dfn>capture the new state</dfn> for {{ViewTransition}} |transition|:

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. Let |namedElements| be |transition|'s [=ViewTransition/named elements=].

		1. Let |usedTransitionNames| be a new [=/set=] of strings.

		1. [=list/For each=] |element| of every [=/element=] that is [=/connected=],
			and has a [=node document=] equal to |document|,
			in [paint order](https://drafts.csswg.org/css2/#painting-order):

			1. If any [=flat tree=] ancestor of this |element| [=skips its contents=], then [=continue=].

			1. Let |transitionName| be |element|'s [=document-scoped view transition name=].

			1. If |transitionName| is ''view-transition-name/none'',
				or |element| is [=element-not-rendered|not rendered=],
				then [=continue=].

			1. If |element| has more than one [=box fragment=], then [=continue=].

			1. If |usedTransitionNames| [=list/contains=] |transitionName|,
				then return failure.

			1. [=set/Append=] |transitionName| to |usedTransitionNames|.

			1. If |namedElements|[|transitionName|] does not [=map/exist=],
				then set |namedElements|[|transitionName|] to a new [=captured element=] struct.

				Note: We intentionally add this struct to the end of this ordered map.
					This implies than names which only exist in the new DOM (entry animations) will be painted on top of names only in the old DOM (exit animations) and names in both DOMs (paired animations).
					This might not be the right layering for all cases. See <a href="https://github.com/w3c/csswg-drafts/issues/8941">issue 8941</a>.

			1. If |namedElements|[|transitionName|]'s [=new element=] is not null, then set |namedElements|[|transitionName|]'s [=captured element/duplicate=] to true.

			1. Set |namedElements|[|transitionName|]'s [=new element=] to |element|.
	</div>

### [=Setup transition pseudo-elements=] ### {#setup-transition-pseudo-elements-algorithm}

	<div algorithm>
		To <dfn>setup transition pseudo-elements</dfn> for a {{ViewTransition}} |transition|:

		Note: This algorithm constructs the pseudo-element tree for the transition,
		and generates initial styles.
		The structure of the pseudo-tree is covered at a higher level in [[#view-transition-pseudos]].

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. [=map/For each=] |transitionName| → |capturedElement| of |transition|'s [=ViewTransition/named elements=]:

			1. Let |group| be a new ''::view-transition-group()'',
				with its [=view transition name=] set to |transitionName|.

			1. Append |group| to the ''::view-transition'' [=pseudo-element=] on |transition|'s [=ViewTransition/root element=].

			1. Let |imagePair| be a new ''::view-transition-image-pair()'',
				with its [=view transition name=] set to |transitionName|.

			1. Append |imagePair| to |group|.

			1. If |capturedElement|'s [=captured element/old image=] is not null, then:

				1. Let |old| be a new ''::view-transition-old()'',
					with its [=view transition name=] set to |transitionName|,
					displaying |capturedElement|'s [=captured element/old image=]
					as its [=replaced element|replaced=] content.

				1. Append |old| to |imagePair|.

			1. If |capturedElement|'s [=new element=] is not null, then:

				1. Let |new| be a new ''::view-transition-new()'',
					with its [=view transition name=] set to |transitionName|.

					Note: The styling of this pseudo is handled in [=update pseudo-element styles=].

				1. Append |new| to |imagePair|.

			1. If |capturedElement|'s [=captured element/old image=] is null, then:
				1. [=Assert=]: |capturedElement|'s [=captured element/new element=] is not null.

				1. Set |capturedElement|'s [=captured element/image animation name rule=] to a new {{CSSStyleRule}} representing the following CSS,
					and append it to |document|'s [=document/dynamic view transition style sheet=]:

					<!-- deliberately using <pre> so the example can contain <var> references -->
					<pre highlight="css">
						:root::view-transition-new(<var>transitionName</var>) {
							animation-name: -ua-view-transition-fade-in;
						}
					</pre>

					Note: The above code example contains variables to be replaced.

			1. If |capturedElement|'s [=captured element/new element=] is null, then:
				1. [=Assert=]: |capturedElement|'s [=captured element/old image=] is not null.

				1. Set |capturedElement|'s [=captured element/image animation name rule=] to a new {{CSSStyleRule}} representing the following CSS,
					and append it to |document|'s [=document/dynamic view transition style sheet=]:

					<!-- deliberately using <pre> so the example can contain <var> references -->
					<pre highlight="css">
						:root::view-transition-old(<var>transitionName</var>) {
							animation-name: -ua-view-transition-fade-out;
						}
					</pre>

					Note: The above code example contains variables to be replaced.

			1. If both of |capturedElement|'s [=captured element/old image=] and [=captured element/new element=]
				are not null, then:

				1. Let |transform| be |capturedElement|'s [=captured element/old transform=].

				1. Let |width| be |capturedElement|'s [=captured element/old width=].

				1. Let |height| be |capturedElement|'s [=captured element/old height=].

				1. Let |backdropFilter| be |capturedElement|'s [=captured element/old backdrop-filter=].

				1. Set |capturedElement|'s [=captured element/group keyframes=] to a new {{CSSKeyframesRule}} representing the following CSS,
					and append it to |document|'s [=document/dynamic view transition style sheet=]:

					<!-- deliberately using <pre> so the example can contain <var> references -->
					<pre highlight="css">
						@keyframes -ua-view-transition-group-anim-<var>transitionName</var> {
							from {
								transform: <var>transform</var>;
								width: <var>width</var>;
								height: <var>height</var>;
								backdrop-filter: <var>backdropFilter</var>;
							}
						}
					</pre>

					Note: The above code example contains variables to be replaced.

				1. Set |capturedElement|'s [=captured element/group animation name rule=] to a new {{CSSStyleRule}} representing the following CSS,
					and append it to |document|'s [=document/dynamic view transition style sheet=]:

					<!-- deliberately using <pre> so the example can contain <var> references -->
					<pre highlight="css">
						:root::view-transition-group(<var>transitionName</var>) {
							animation-name: -ua-view-transition-group-anim-<var>transitionName</var>;
						}
					</pre>

					Note: The above code example contains variables to be replaced.

				1. Set |capturedElement|'s [=captured element/image pair isolation rule=] to a new {{CSSStyleRule}} representing the following CSS,
					and append it to |document|'s [=document/dynamic view transition style sheet=]:

					<!-- deliberately using <pre> so the example can contain <var> references -->
					<pre highlight="css">
						:root::view-transition-image-pair(<var>transitionName</var>) {
							isolation: isolate;
						}
					</pre>

					Note: The above code example contains variables to be replaced.

				1. Set |capturedElement|'s [=captured element/image animation name rule=] to a new {{CSSStyleRule}} representing the following CSS,
					and append it to |document|'s [=document/dynamic view transition style sheet=]:

					<!-- deliberately using <pre> so the example can contain <var> references -->
					<pre highlight="css">
						:root::view-transition-old(<var>transitionName</var>) {
							animation-name: -ua-view-transition-fade-out, -ua-mix-blend-mode-plus-lighter;
						}
						:root::view-transition-new(<var>transitionName</var>) {
							animation-name: -ua-view-transition-fade-in, -ua-mix-blend-mode-plus-lighter;
						}
					</pre>

					Note: The above code example contains variables to be replaced.

					Note: ''mix-blend-mode: plus-lighter'' ensures
					that the blending of identical pixels from the old and new images
					results in the same color value as those pixels,
					and achieves a “correct” cross-fade.
	</div>

## Setup cross-document view transition ## {#setup-crossdoc-view-transition-algo}

### Setting up the view transition in the old {{Document}} ### {#setup-old-document-vt}

#### Check eligibility for outbound cross-document view transition #### {#check-eligibility}
<div algorithm>
	To check if a <dfn export>navigation can trigger a cross-document view-transition?</dfn> given
	a {{Document}} |oldDocument|, a {{Document}} |newDocument|, a {{NavigationType}} |navigationType|, and a boolean |isBrowserUINavigation|:

		Note: this is called during navigation, potentially [=in parallel=].

	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 false.

	1. If |oldDocument|'s [=can initiate outbound view transition=] is false, then return false.

	1. If |navigationType| is {{NavigationType/reload}}, then return false.

	1. If |oldDocument|'s [=Document/origin=] is not [=same origin=] as |newDocument|'s [=Document/origin=], then return false.

	1. If |newDocument| [=was created via cross-origin redirects=] and |newDocument|'s [=latest entry=] is null, then return false.

		Note: A [=Document=]'s [=latest entry=] would be null if this is a new navigation, rather than a restore from BFCache.

	1. If |navigationType| is {{NavigationType/traverse}}, then return true.

	1. If |isBrowserUINavigation| is true, then return false.

	1. Return true.
</div>

#### Setup the outbound transition when ready to swap pages #### {#setup-outbound-transition}

<div algorithm>
	To <dfn export>setup cross-document view-transition</dfn> given a {{Document}} |oldDocument|,
	a {{Document}} |newDocument|, and |proceedWithNavigation|, which is an algorithm accepting nothing:

	Note: This is called from the HTML spec.

	1. [=Assert=]: These steps are running as part of a [=task=] queued on |oldDocument|.

	1. If |oldDocument|'s [=can initiate outbound view transition=] is false, then return null.

	1. Let |transitionTypesFromRule| be the result of [=Resolve @view-transition rule|resolving the @view-transition rule=] for |oldDocument|.

	1. [=Assert=]: |transitionTypesFromRule| is not "<code>skip transition</code>".

		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 we fire the {{Window/pagereveal}} event.

	1. If |oldDocument|'s [=active view transition=] is not null,
		then [=skip the view transition|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.

	1. Let |outboundTransition| be a new {{ViewTransition}} object in |oldDocument|'s [=relevant Realm=].

	1. Set |outboundTransition|'s [=ViewTransition/active types=] to |transitionTypesFromRule|.

		Note: the [=ViewTransition/active types=] are not shared between documents.
		Manipulating the {{ViewTransition/types}} in the old document does not affect the types in |newDocument|,
		which would be read from the [=@view-transition/types=] descriptor once |newDocument| is revealed.

		Note: the {{ViewTransition}} is skipped once the old document is hidden.

	1. Set |outboundTransition|'s [=outbound post-capture steps=] to the following steps given a [=view transition params=]-or-null |params|:
		1. Set |newDocument|'s [=inbound view transition params=] to |params|.

			Note: The inbound transition is activated after the dispatch of {{Window/pagereveal}} to ensure mutations made in this event apply to the captured new state.

		1. To skip the transition after a timeout, the user agent may perform the following steps [=in parallel=]:

			1. Wait for an [=implementation-defined=] [=duration=].

			1. [=Queue a global task=] on the [=DOM manipulation task source=] given |newDocument|'s [=relevant global object=] to perform the following step:

				1. If |newDocument|'s [=inbound view transition params=] is |params|, then set |newDocument|'s [=inbound view transition params=] to null.

		1. Call |proceedWithNavigation|.

	1. Set |oldDocument|'s [=active view transition=] to |outboundTransition|.

		Note: The process continues in [=perform pending transition operations=].

	1. The user agent should display the currently displayed frame until either:
		* The {{Window/pagereveal}} event is fired.
		* its [=active view transition=]'s [=ViewTransition/phase=] is "`done`".

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

	1. Return |outboundTransition|.
</div>

#### Update the opt-in flag to reflect the current state #### {#update-opt-in}

<div algorithm="update-opt-in-for-outbound">
To <dfn>update the opt-in state for outbound transitions</dfn> for a {{Document}} |document|:
	1. If |document| [=has been revealed=], and the result of [=resolve @view-transition rule|resolving the @view-transition rule=] is not "<code>skip transition</code>",
		then set |document|'s [=can initiate outbound view transition=] to true.
	1. Otherwise, set |document|'s [=can initiate outbound view transition=] to false.
</div>

#### Proceed with navigation if view transition is skipped #### {#proceed-if-skipped}

<div algorithm="additional skip steps">
	Append the following steps to [=skip the view transition=] given a {{ViewTransition}} |transition|:
		1. If |transition|'s [=outbound post-capture steps=] is not null, then run |transition|'s [=outbound post-capture steps=] with null.

	Note: This is written in a monkey-patch manner, and will be merged into the algorithm once the L1 spec graduates.
</div>

#### Proceed with cross-document view transition after capturing the old state #### {#cross-doc-after-capture}

<div algorithm="additional transition operation">
Prepend the following step to the [=Perform pending transition operations=] algorithm given a {{Document}} |document|:
	1. If |document|'s [=active view transition=] is not null and its [=outbound post-capture steps=] is not null, then:

		1. Assert: |document|'s [=active view transition=]'s  [=ViewTransition/phase=] is "`pending-capture`".

		1. Let |viewTransitionParams| be null;

		1. Set |document|'s [=document/rendering suppression for view transitions=] to true.

			Issue: Though [=capture the old state=] appears here as a synchronous step, it is in fact an asynchronous step
			as rendering an element into an image cannot be done synchronously. This should be more explicit in the L1 spec.

		1. [=Capture the old state=] for |transition|.

		1. If this succeeded, then set |viewTransitionParams| to a new [=view transition params=] whose
			[=view transition params/named elements=] is a [=map/clone=] of |transition|'s [=ViewTransition/named elements=],
			and whose [=view transition params/initial snapshot containing block size=] is |transition|'s [=ViewTransition/initial snapshot containing block size=].

		1. Set |document|'s [=document/rendering suppression for view transitions=] to false.

		1. Call |transition|'s [=outbound post-capture steps=] given |viewTransitionParams|.

	Note: This is written in a monkey-patch manner, and will be merged into the algorithm once the L1 spec graduates.
</div>

### Activating the view transition in the new {{Document}} ### {#access-view-transition-in-new-doc}

<div algorithm>
	To <dfn export>resolve inbound cross-document view-transition</dfn> for {{Document}} |document|:

	1. [=Assert=]: |document| is [=fully active=].

	1. [=Assert=]: |document| [=has been revealed=] is true.

	1. [=Update the opt-in state for outbound transitions=] for |document|.

	1. Let |inboundViewTransitionParams| be |document|'s [=inbound view transition params=].

	1. If |inboundViewTransitionParams| is null, then return null.

	1. Set |document|'s [=inbound view transition params=] to null.

	1. If |document|'s [=active view transition=] is not null, then return null.

		Note: this means that starting a same-document transition before revealing the document would cancel a pending cross-document transition.

	1. [=Resolve @view-transition rule=] for |document| and let |resolvedRule| be the result.

	1. If |resolvedRule| is "<code>skip transition</code>", then return null.

	1. Let |transition| be a new {{ViewTransition}} in |document|'s [=relevant Realm=],
		whose [=ViewTransition/named elements=] is |inboundViewTransitionParams|'s [=view transition params/named elements=],
		and [=ViewTransition/initial snapshot containing block size=] is |inboundViewTransitionParams|'s [=view transition params/initial snapshot containing block size=].

	1. Set |document|'s [=active view transition=] to |transition|.

	1. [=Resolve=] |transition|’s [=ViewTransition/update callback done promise=] with undefined.

	1. Set |transition|’s [=ViewTransition/phase=] to "`update-callback-called`".

	1. Set |transition|'s [=ViewTransition/active types=] to |resolvedRule|.


	1. Return |transition|.
</div>

### Capturing the 'view-transition-class' ### {#vt-class-algorithms}

<div algorithm="additional capture steps">
When capturing the old or new state for an element, perform the following steps given a [=captured element=] |capture| and an [=element=] |element|:

	1. Set |capture|'s [=captured element/class list=] to the [=computed value=] of |element|'s 'view-transition-class', if it is associated with |element|'s [=node document=].

	Note: This is written in a monkey-patch manner, and will be merged into the algorithm once the L1 spec graduates.
</div>

### Capturing and applying 'view-transition-group' ### {#vt-group-algorithm}

#### Resolving the 'view-transition-group' value #### {#vt-group-value-resolve}

<div algorithm="resolve the document-scoped view-transition-group">
To get the <dfn>document-scoped view transition group</dfn> of an {{Element}} |element|, perform the following steps:

	1. Let |computedGroup| be the [=computed value=] of element's 'view-transition-group' property.
	1. If |computedGroup| is associated with |element|'s [=node document=], return |computedGroup|.
	1. Return ''view-transition-group/normal''.
</div>

#### Resolving the containing group name #### {#vt-containing-group-name-resolve}

<div algorithm="resolve nearest containing group name">

To resolve the <dfn>nearest containing group name</dfn> of an {{Element}} |element|, perform the following steps given a {{ViewTransition}} |viewTransition|:
	1. Assert: |element| participates in |viewTransition|.
	1. Let |ancestorGroup| be |element|'s nearest [=flat tree=] [=tree/ancestor=] who participates in |viewTransition| and whose [=document-scoped view transition group=] is not ''view-transition-group/normal''.
	1. If |ancestorGroup| exists, return |ancestorGroup|'s [=document-scoped view transition name=].
	1. Return ''view-transition-name/none''.
</div>

#### Resolving the group name #### {#vt-group-name-resolve}

<div algorithm="resolve used group name">
To resolve the <dfn>used group name</dfn> of an {{Element}} |element|, perform the following steps given a {{ViewTransition}} |transition|:

	1. Assert: |element| participates in |transition|.
	1. Let |group| be |element|'s [=document-scoped view transition group=].
	1. Let |containingGroupName| be |element|'s [=nearest containing group name=] given |transition|.
	1. Return the first matching statement, switching on |group|:

		* ''view-transition-group/normal'', ''view-transition-group/contain'': |containingGroupName|.
			Note: an element with ''view-transition-group/contain'' becomes the [=nearest containing group name=] for its descendants.
		* ''view-transition-group/nearest'': The [=document-scoped view transition name=] of the element's nearest [=flat tree=] ancestor which participates in the |transition|.
		* ''view-transition-group/<custom-ident>'': |group| if the element has a [=flat tree=] [=tree/ancestor=] whose [=document-scoped view transition name=] is |group| and participates in |transition|; Otherwise |containingGroupName|.
</div>

#### Compute the old 'view-transition-group' #### {#vt-group-capture-old}

<div algorithm="additional old capture steps (group)">
When  [[css-view-transitions-1#capture-old-state-algorithm|capturing the old state for an element]], perform the following steps given a [=captured element=] |capturedElement|, a {{ViewTransition}} |transition|, and an [=element=] |element|:

	1. Set |capturedElement|'s [=captured element/containing group name=] to |element|'s [=used group name=] given |transition|.

	1. Set |capturedElement|'s [=captured element/old border width=] to |element|'s [=used value|used=] 'border-width'.
</div>

#### Compute the new 'view-transition-group' #### {#vt-group-capture-new}

<div algorithm="additional new capture steps (group)">
When [[css-view-transitions-1#capture-new-state-algorithm|capturing the new state for an element]], perform the following steps given a [=captured element=] |capturedElement| a {{ViewTransition}} |transition|, and an [=element=] |element|:

	1. Set |capturedElement|'s [=captured element/containing group name=] to |element|'s [=used group name=] given |transition|.
</div>

#### Reparent a ''::view-transition-group()'' to its specified containing group #### {#vt-group-reparent}

<div algorithm="additional pseudo-element setup steps (group)">
When [[css-view-transitions-1#setup-transition-pseudo-elements-algorithm|setting up the transition pseudo-element]] for a [=captured element=] |capturedElement|, given a |transitionName| and a |transition|:

	1. Let |containingGroupName| be |capturedElement|'s [=containing group name=].

	1. If |containingGroupName| is null, return.

	1. Let |groupContainerElement| be |transition|'s [=ViewTransition/named elements=][|containingGroupName|].

	1. Let |group| be the ''::view-transition-group()'',
		whose [=view transition name=] is set to |transitionName|.

	1. Let |childrenContainerGroup| be the ''::view-transition-group-children()'' whose [=view transition name=] is set to |containingGroupName|.

	1. If |childrenContainerGroup| does not exist:
		Generate it and append to the ''::view-transition-group()'',
		whose [=view transition name=] is set to |containingGroupName|.

	1. Append |group| to |childrenContainerGroup|.

	1.  When setting the animation keyframes given |transform|, [=adjust nested view transition group transform|adjust=] |transform| given |groupContainerElement|.

	Note: It is TBD how this is resolved when the old and new groups mismatch. See <a href="https://github.com/w3c/csswg-drafts/issues/10631">Issue 10631</a>.
</div>

#### Adjust the group's 'transform' to be relative to its containing ''::view-transition-group()'' #### {#vt-group-transform-adjust}

<div algorithm="additional pseudo-element style update steps (group)">
When [[css-view-transitions-1#style-transition-pseudo-elements-algorithm|updating the style of the transition pseudo-element]], perform the following steps before setting the [=captured element/group styles rule=], given a [=captured element=] |capturedElement|, given a |transform| and a {{ViewTransition}} |transition|:

	1. Let |childrenTransform| be |transform|.

	1. Set |capturedElement|'s [=transform from snapshot containing block=] to |transform|.

	1. If |capturedElement|'s [=containing group name=] is not null, then:

		1. Let |groupContainerElement| be |transition|'s [=ViewTransition/named elements=][|capturedElement|'s [=containing group name=].

		1. [=adjust nested view transition group transform|Adjust=] |transform| given |groupContainerElement|.

To <dfn>adjust nested view transition group transform</dfn> given a |transform| and a [=captured element=] |groupContainerElement|:
	1. Let |childrenTransform| be |groupContainerElement|'s [=transform from snapshot containing block=].

	1. If |childrenTransform| is null, set it to |groupContainerElement|'s [=captured element/old transform=].

	1. Translate |childrenTransform| by |groupContainerElement|'s [=captured element/old border width=]'s left and top components.

	1. Multiply |transform| by the inverse matrix of |childrenTransform|.
</div>

## [=Call the update callback=] ## {#call-dom-update-callback-algorithm}

	<div algorithm>
		To <dfn>call the update callback</dfn> of a {{ViewTransition}} |transition|:

		Note: This is guaranteed to happen for every {{ViewTransition}},
		even if the transition is [=Skip the view transition|skipped=].
		The reasons for this are discussed in [[#transitions-as-enhancements]].

		1. [=Assert=]: |transition|'s [=ViewTransition/phase=] is "`done`", or before "`update-callback-called`".

		1. If |transition|'s [=ViewTransition/phase=] is not "`done`", then set |transition|'s [=ViewTransition/phase=] to "`update-callback-called`".

		1. Let |callbackPromise| be null.

		1. If |transition|'s [=ViewTransition/update callback=] is null,
			then set |callbackPromise| to [=a promise resolved with=] undefined,
			in |transition|'s [=relevant Realm=].

		1. Otherwise, set |callbackPromise| to the result of [=/invoking=] |transition|'s [=ViewTransition/update callback=].

		1. Let |fulfillSteps| be to following steps:
			1. [=Resolve=] |transition|'s [=ViewTransition/update callback done promise=] with undefined.

			1. [=Activate view transition|Activate=] |transition|.

		1. Let |rejectSteps| be the following steps given |reason|:
			1. [=Reject=] |transition|'s [=ViewTransition/update callback done promise=] with |reason|.

			1. If |transition|'s [=ViewTransition/phase=] is "`done`", then return.

				Note: This happens if |transition| was [=skip the view transition|skipped=] before this point.

			1. [=Mark as handled=] |transition|'s [=ViewTransition/ready promise=].

				Note: |transition|'s [=ViewTransition/update callback done promise=] will provide the {{unhandledrejection}}.
				This step avoids a duplicate.

			1. [=Skip the view transition=] |transition| with |reason|.

		1. [=React=] to |callbackPromise| with |fulfillSteps| and |rejectSteps|.

		1. To skip a transition after a timeout, the user agent may perform the following steps [=in parallel=]:
			1. Wait for an implementation-defined [=duration=].

			1. [=Queue a global task=] on the [=DOM manipulation task source=],
				given |transition|'s [=relevant global object=], to perform the following steps:

				1. If |transition|'s [=ViewTransition/phase=] is "`done`", then return.

					Note: This happens if |transition| was [=skip the view transition|skipped=] before this point.

				1. [=skip the view transition|Skip=] |transition| with a "{{TimeoutError}}" {{DOMException}}.

	</div>

	<div algorithm>
		To <dfn>schedule the update callback</dfn> given a {{ViewTransition}} |transition|:
			1. [=list/Append=] |transition| to |transition|'s [=relevant settings object=]'s [=update callback queue=].
			1. [=Queue a global task=] on the [=DOM manipulation task source=],
				given |transition|'s [=relevant global object=], to [=flush the update callback queue=].

	</div>
	<div algorithm>
		To <dfn>flush the update callback queue</dfn> given a {{Document}} |document|:
			1. [=list/For each=] |transition| in |document|'s [=update callback queue=], [=call the update callback=] given |transition|.

			1. Set |document|'s [=update callback queue=] to an empty list.
	</div>

## [=Skip the view transition=] ## {#skip-the-view-transition-algorithm}

	<div algorithm>
		To <dfn>skip the view transition</dfn> for {{ViewTransition}} |transition| with reason |reason|:

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. [=Assert=]: |transition|'s [=ViewTransition/phase=] is not "`done`".

		1. If |transition|'s [=ViewTransition/phase=] is before "`update-callback-called`",
			then [=schedule the update callback=] for |transition|.

		1. Set [=document/rendering suppression for view transitions=] to false.

		1. If |document|'s [=document/active view transition=] is |transition|,
			[=Clear view transition=] |transition|.

		1. Set |transition|'s [=ViewTransition/phase=] to "`done`".

		1. [=Reject=] |transition|'s [=ViewTransition/ready promise=] with |reason|.

			Note: The [=ViewTransition/ready promise=] may already be resolved at this point,
			if {{ViewTransition/skipTransition()}} is called after we start animating.
			In that case, this step is a no-op.

		1. [=Resolve=] |transition|'s [=ViewTransition/finished promise=] with the result of [=reacting=] to |transition|'s [=ViewTransition/update callback done promise=]:

			- If the promise was fulfilled, then return undefined.

			Note: Since the rejection of |transition|'s [=ViewTransition/update callback done promise=] isn't explicitly handled here,
			if |transition|'s [=ViewTransition/update callback done promise=] rejects,
			then |transition|'s [=ViewTransition/finished promise=] will reject with the same reason.
	</div>

## View transition page-visibility change steps ## {#page-visibility-change-steps}

	<div algorithm>
		The <dfn export>view transition page-visibility change steps</dfn> given {{Document}} |document| are:

		1. [=Queue a global task=] on the [=DOM manipulation task source=],
				given |document|'s [=relevant global object=],
				to perform the following steps:

			1. If |document|'s [=Document/visibility state=] is "<code>hidden</code>", then:

				1. If |document|'s [=active view transition=] is not null, then [=skip the view transition|skip=] |document|'s [=active view transition=] with an "{{InvalidStateError}}" {{DOMException}}.

			1. Otherwise, [=assert=]: [=active view transition=] is null.

			1. Set |document|'s [=inbound view transition params=] to null.

		Note: this is called from the HTML spec.
	</div>

## [=Capture the image=] ## {#capture-the-image-algorithm}

	<div algorithm>
		To <dfn lt="capture the image|capturing the image">capture the image</dfn> given an [=/element=] |element|, perform the following steps.
		They return an image.

		1. If |element| is the [=document element=], then:

			1. Render the region of document
				(including its [=canvas background=] and any [=Document/top layer=] content)
				that intersects the [=snapshot containing block=],
				on a transparent canvas the size of the [=snapshot containing block=],
				following the [=capture rendering characteristics=], and these additional characteristics:

				- Areas outside |element|'s [=scrolling box=] should be rendered as if they were scrolled to, without moving or resizing the [=layout viewport=].
					This must not trigger events related to scrolling or resizing, such as {{IntersectionObserver}}s.

					<figure>
						<img src="diagrams/phone-browser-with-url.svg" width="202" height="297" alt="A phone browser window, showing a URL bar, a fixed-position element directly beneath it, and some page content beneath that. A scroll bar indicates the page has been scrolled significantly.">
						<img src="diagrams/phone-browser-without-url.svg" width="202" height="297" alt="The captured snapshot. It shows that content beneath the URL bar was included in the capture.">
						<figcaption>
							An example of what the user sees compared to the captured snapshot.
							This example assumes the root is the only element with a transition name.
						</figcaption>
					</figure>

				- Areas that cannot be scrolled to (i.e. they are out of scrolling bounds),
					should render the [=canvas background=].

					<figure>
						<img src="diagrams/phone-browser-scrolled-to-top-with-url.svg" width="202" height="297" alt="A phone browser window, showing a URL bar, and some content beneath. A scroll bar indicates the page is scrolled to the top.">
						<img src="diagrams/phone-browser-scrolled-to-top-without-url.svg" width="202" height="297" alt="The captured snapshot. It shows the area underneath the URL bar as the same color as the rest of the document.">
						<figcaption>
							An example of what the user sees compared to the captured snapshot.
							This example assumes the root is the only element with a transition name.
						</figcaption>
					</figure>

			1. Return this canvas as an image.
				The natural size of the image is equal to the [=snapshot containing block=].

		1. Otherwise:

			1. Render |element| and its [=tree/descendants=],
				at the same size it appears in its [=node document=],
				over an infinite transparent canvas,
				following the [=capture rendering characteristics=].

			1. Return the portion of this canvas that includes |element|'s [=ink overflow rectangle=] as an image.
				The [=natural dimensions=] of this image must be those of its [=principal box|principal=] [=border box=],
				and its origin must correspond to that [=border box=]'s origin,
				such that the image represents the contents of this [=border box=]
				and any captured [=ink overflow=] is represented outside these bounds.

				Note: When this image is rendered as a [=replaced element=] at its [=natural size=],
				it will display with the size and contents of element’s [=principal box=],
				with any captured [=ink overflow=] overflowing its [=content box=].
	</div>

### [=Capture rendering characteristics=] ### {#capture-rendering-characteristics-algorithm}

	<div algorithm>
		The <dfn>capture rendering characteristics</dfn> are as follows:

		* If the referenced element has a transform applied to it (or its ancestors),
			then the transform is ignored.

			Note: This transform is applied to the snapshot using the `transform` property of the associated ''::view-transition-group'' pseudo-element.

		* Effects applied on the element and its descendants, such as 'opacity' and 'filter', are applied to the capture.
			Effects applied to the element from its ancestors are ignored.

		* Implementations may clip the rendered contents if the [=ink overflow rectangle=] exceeds some [=implementation-defined=] maximum.
			However, the captured image should include, at the very least, the contents of |element| that intersect with the [=snapshot containing block=].
			Implementations may adjust the rasterization quality to account for elements with a large [=ink overflow area=] that are transformed into view.

		* Implementations may also adjust the rasterization quality for elements whose [=ink overflow rectangle=] does not intersect with the [=snapshot containing block=].
			To avoid a broken experience if the element ends up becoming visible, the captured image should include, at the very least, some low-quality representation of the contents rather than transparent pixels.

			Note: This allows efficiency in resource usage and rasterization performance for elements that are away from the viewport and might not become visible at all,
			while maintaining a visual effect close enough to the author's intent.

		* [=list/For each=] |descendant| of [=shadow-including descendant=] {{Element}} and [=pseudo-element=] of |element|,
			if |descendant| is [=captured in a view transition=],
			then skip painting |descendant|.

			Note: This is necessary since the descendant will generate its own snapshot which will be displayed and animated independently.
	</div>

	<div algorithm="delay finish for promise">
		To <dfn>delay finish for promise</dfn>, given promise |p|:

		1. If |p| is settled, abort the remainder of the steps and return.

		1. Increment [=this=]'s [=wait until promise count=].

		1. [=Upon fulfillment=] or [=upon rejection|rejection=] of |p|:

				1. Decrement [=this=]'s [=wait until promise count=].

				Note: If [=this=]'s [=wait until promise count=] is 0, the transition will finish at the next [=rendering opportunity=].
	</div>

## [=Handle transition frame=] ## {#handle-transition-frame-algorithm}

	<div algorithm>
		To <dfn>handle transition frame</dfn> given a {{ViewTransition}} |transition|:

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. Let |hasActiveAnimations| be a boolean, initially false.

		1. [=list/For each=] |element| of the [=tree/inclusive descendants=] of the ''::view-transition'' [=pseudo-element=] on |transition|'s [=ViewTransition/root element=]:
			1. For each |animation| whose [=timeline=] is a [=document timeline=] associated with |document|,
				and contains at least one [=animation/associated effect=] whose [=effect target=] is |element|,
				set |hasActiveAnimations| to true if any of the following conditions are true:

				- |animation|'s [=animation/play state=] is [=play state/paused=] or [=play state/running=].

				- |document|'s [=pending animation event queue=] has any events associated with |animation|.

		1. If |hasActiveAnimations| is false and [=this=]'s [=wait until promise count=] is 0:

			1. Set |transition|'s [=ViewTransition/phase=] to "`done`".

			1. [=Clear view transition=] |transition|.

			1. [=Resolve=] |transition|'s [=ViewTransition/finished promise=].

			1. Return.

		1. If |transition|'s [=ViewTransition/initial snapshot containing block size=] is not equal to the [=snapshot containing block size=],
			then [=skip the view transition=] for |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
			and return.

		1. [=Update pseudo-element styles=] for |transition|.

			If failure is returned, then [=skip the view transition=] for |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
			and return.

			Note: The above implies that a change in incoming element's size or position will cause a new keyframe to be generated.
				This can cause a visual jump.
				We could retarget smoothly but don't have a use-case to justify the complexity.
				See [issue 7813](https://github.com/w3c/csswg-drafts/issues/7813) for details.
	</div>

## [=Update pseudo-element styles=] ## {#style-transition-pseudo-elements-algorithm}

	<div algorithm>
		To <dfn>update pseudo-element styles</dfn> for a {{ViewTransition}} |transition|:

		1. [=map/For each=] |transitionName| → |capturedElement| of |transition|'s [=ViewTransition/named elements=]:

			1. Let |width|, |height|, |transform|, |writingMode|, |direction|, |textOrientation|, |mixBlendMode|, |backdropFilter| and |colorScheme| be null.

			1. If |capturedElement|'s [=new element=] is null, then:

				1. Set |width| to |capturedElement|'s [=captured element/old width=].

				1. Set |height| to |capturedElement|'s [=captured element/old height=].

				1. Set |transform| to |capturedElement|'s [=captured element/old transform=].

				1. Set |writingMode| to |capturedElement|'s [=captured element/old writing-mode=].

				1. Set |direction| to |capturedElement|'s [=captured element/old direction=].

				1. Set |textOrientation| to |capturedElement|'s [=captured element/old text-orientation=].

				1. Set |mixBlendMode| to |capturedElement|'s [=captured element/old mix-blend-mode=].

				1. Set |backdropFilter| to |capturedElement|'s [=captured element/old backdrop-filter=].

				1. Set |colorScheme| to |capturedElement|'s [=captured element/old color-scheme=].

			1. Otherwise:

				1. Return failure if any of the following conditions are true:
					- |capturedElement|'s [=new element=] has a [=flat tree=] ancestor that [=skips its contents=].

					- |capturedElement|'s [=new element=] is [=element-not-rendered|not rendered=].

					- |capturedElement| has more than one [=box fragment=].

					Note: Other rendering constraints are enforced via |capturedElement|'s [=new element=] being [=captured in a view transition=].

				1. Let |newRect| be the [=snapshot containing block=] if |capturedElement|'s [=new element=] is the [=document element=],
					otherwise, |capturedElement|'s [=border box=].

				1. Set |width| to the current width of |newRect|.

				1. Set |height| to the current height of |newRect|.

				1. Set |transform| to a transform that would map |newRect| from the [=snapshot containing block origin=] to its current visual position.

				1. Set |writingMode| to the [=computed value=] of 'writing-mode' on |capturedElement|'s [=new element=].

				1. Set |direction| to the [=computed value=] of 'direction' on |capturedElement|'s [=new element=].

				1. Set |textOrientation| to the [=computed value=] of 'text-orientation' on |capturedElement|'s [=new element=].

				1. Set |mixBlendMode| to the [=computed value=] of 'mix-blend-mode' on |capturedElement|'s [=new element=].

				1. Set |backdropFilter| to the [=computed value=] of 'backdrop-filter' on |capturedElement|'s [=new element=].

				1. Set |colorScheme| to the [=computed value=] of 'color-scheme' on |capturedElement|'s [=new element=].

			1. If |capturedElement|'s [=captured element/group styles rule=] is null,
				then set |capturedElement|'s [=captured element/group styles rule=] to a new {{CSSStyleRule}} representing the following CSS,
				and append it to |transition|'s [=relevant global object's=] [=associated document=]'s [=document/dynamic view transition style sheet=].

				Otherwise, update |capturedElement|'s [=captured element/group styles rule=] to match the following CSS:

				<!-- deliberately using <pre> so the example can contain <var> references -->
				<pre highlight="css">
					:root::view-transition-group(<var>transitionName</var>) {
						width: <var>width</var>;
						height: <var>height</var>;
						transform: <var>transform</var>;
						writing-mode: <var>writingMode</var>;
						direction: <var>direction</var>;
						text-orientation: <var>textOrientation</var>;
						mix-blend-mode: <var>mixBlendMode</var>;
						backdrop-filter: <var>backdropFilter</var>;
						color-scheme: <var>colorScheme</var>;
					}
				</pre>

				Note: The above code example contains variables to be replaced.

			1. If |capturedElement|'s [=new element=] is not null, then:

				1. Let |new| be the ''::view-transition-new()'' with the [=view transition name=] |transitionName|.

				1. Set |new|'s [=replaced element=] content to the result of [=capturing the image=] of |capturedElement|'s [=new element=].

		This algorithm must be executed to update styles in [=user-agent origin=] if its effects can be observed by a web API.

		Note: An example of such a web API is `window.getComputedStyle(document.documentElement, "::view-transition")`.
	</div>

## [=Clear view transition=] ## {#clear-view-transition-algorithm}

	<div algorithm>
		To <dfn>clear view transition</dfn> of a {{ViewTransition}} |transition|:

		1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

		1. [=Assert=]: |document|'s [=document/active view transition=] is |transition|.

		1. [=list/For each=] |capturedElement| of |transition|'s [=ViewTransition/named elements=]' [=map/values=]:

			1. If |capturedElement|'s [=captured element/new element=] is not null,
				then set |capturedElement|'s [=captured element/new element=]'s [=captured in a view transition=] to false.

			1. [=list/For each=] |style| of |capturedElement|'s [=captured element/style definitions=]:

				1. If |style| is not null,
					and |style| is in |document|'s [=document/dynamic view transition style sheet=],
					then remove |style| from |document|'s [=document/dynamic view transition style sheet=].

		1. Remove all children of the ''::view-transition'' [=pseudo-element=] on |transition|'s [=ViewTransition/root element=].

		1. Set |document|'s [=document/active view transition=] to null.
	</div>


<h2 id="priv" class="no-num">Privacy Considerations</h2>

This specification introduces no new privacy considerations.

<h2 id="sec" class="no-num">Security Considerations</h2>

The images generated using [=capture the image=] algorithm could contain cross-origin data (if the Document is embedding cross-origin resources) or sensitive information like visited links.
The implementations must ensure this data can not be accessed by the Document.
This should be feasible since access to this data should already be prevented in the default rendering of the Document.

To prevent cross-origin issues, at this point cross-document view transitions can only be enabled for
same-origin navigations. As discussed in <a href="https://github.com/WICG/view-transitions/issues/200">WICG/view-transitions#200</a>,
this still presents two potential threats:

1. The [=environment settings object/cross-origin isolated capability=] in both documents might be different.
	This can cause a situation where a {{Document}} that is [=environment settings object/cross-origin isolated capability|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.

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

	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 [=Document/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.

1. 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 <a href="https://github.com/w3c/csswg-drafts/issues/8684">Issue #8684</a> and
<a href="https://github.com/WICG/view-transitions/issues/200">WICG/view-transitions#200</a> for
detailed discussion.

<h2 class="no-num non-normative" id="changes">Appendix A. Changes</h2>

This appendix is <em>informative</em>.

<h3 id="changes-since-2024-05-16">
Changes from <a href="https://www.w3.org/TR/2024/WD-css-view-transitions-2-20240516/">2024-05-16 Working Draft</a>
</h3>

* Add description of nested view transition groups, which was partially missing.
* Update wording to use `opacity: 0` instead of `visibility: hidden`. See <a href="https://github.com/w3c/csswg-drafts/issues/12629">issue 12629</a>.
* Replace `contain: view-transition` with `view-transition-scope` property (<a href="https://github.com/w3c/csswg-drafts/issues/13123">pending #13123</a>)
* Reorder sections, editorial changes, added several issues, updated scoped view transition wording.
* Add waitUntil() (<a href="https://github.com/w3c/csswg-drafts/issues/9901">#9901</a>)
* First pass at layered capture (<a href="https://github.com/w3c/csswg-drafts/issues/10585">#10585</a>)
* Allow transitions when traversing into a document that was created using cross-origin redirects (<a href="https://github.com/w3c/csswg-drafts/issues/11063">#11063</a>#11063)
* Clarify a few nesting details (<a href="https://github.com/w3c/csswg-drafts/issues/10780">#10780</a> and <a href="https://github.com/w3c/csswg-drafts/issues/10633">#10633</a>)
* Allow `auto` as a keyword for 'view-transition-name' (<a href="https://github.com/w3c/csswg-drafts/issues/8320">#8320</a>)
* Clarify timeout behavior for inbound view transition (<a href="https://github.com/w3c/csswg-drafts/issues/10800">#10800</a>)
* When hiding the document, and inbound cross-document view transition should be skipped. (<a href="https://github.com/w3c/csswg-drafts/issues/9822">#9822</a>)
* Rename UpdateCallback to something more specific.
* Clarify that CSSViewTransitionRule returns an empty string for invalid/missing navigation descriptor (<a href="https://github.com/w3c/csswg-drafts/issues/10654">#10654</a>)
* Initial spec for nested view transitions (<a href="https://github.com/w3c/csswg-drafts/issues/10334">#10334</a>)
* `view-transition-class` is a tree-scoped name (<a href="https://github.com/w3c/csswg-drafts/issues/10529">#10529</a>)
* Stop extending CSSRule with obsolete pattern (See <a href="https://github.com/w3c/csswg-drafts/issues/10606">#10606</a>)
* Added {{Document/activeViewTransition}} attribute (<a href="https://github.com/w3c/csswg-drafts/issues/12407">#12407</a>)
