CSS ::view-transition-old() Pseudo-Element

The ::view-transition-old() pseudo-element represents a static snapshot of an element's previous visual state during a View Transition. It allows developers to customize how the outgoing content disappears, including fade-out, sliding, scaling, and other animations as the webpage changes to its new state.

Syntax

::view-transition-old(name) {
  property: value;
}

The name argument identifies the View Transition group whose old visual state should be styled.

For example:

::view-transition-old(root) {
  animation-duration: 500ms;
}

This changes the animation duration of the old visual state for the root transition.

To target a custom element:

::view-transition-old(featured-card) {
  animation-duration: 400ms;
}

This targets the old visual state of the group named featured-card.

To target all old transition representations:

::view-transition-old(*) {
  animation-duration: 300ms;
}

The universal selector * matches every generated old-state pseudo-element.

The functional pseudo-element also accepts a View Transition class selector:

::view-transition-old(.cards) {
  animation-duration: 400ms;
}

This targets old representations belonging to the View Transition class cards.

The parentheses are required because ::view-transition-old() is a functional pseudo-element.

How It Works

When a View Transition starts, the browser captures the visual appearance of participating elements before the document changes.

That captured appearance becomes the old visual state.

The browser then updates the document, prepares the new state, and creates temporary pseudo-elements to animate between the two appearances.

The process generally follows these steps:

  1. A View Transition begins through JavaScript or an eligible navigation.
  2. The browser captures the old visual state of participating elements.
  3. The document updates to its new state.
  4. The browser prepares the new visual representation.
  5. The browser creates the View Transition pseudo-element tree.
  6. The old snapshot animates out while the new representation animates in.
  7. The temporary pseudo-elements are removed when the transition finishes.

The ::view-transition-old() pseudo-element displays the captured appearance, not the original HTML element itself.

Because it is a static snapshot, subsequent changes to the underlying element do not update that captured image.

The browser normally applies a fade-out animation when both old and new visual states exist.

Developers can customize or replace the default animation using CSS.

The pseudo-element exists only while the corresponding View Transition is active.

View Transition Pseudo-Element Tree

The browser creates a temporary hierarchy of pseudo-elements to display a View Transition.

A simplified structure looks like this:

::view-transition
  ::view-transition-group(root)
    ::view-transition-image-pair(root)
      ::view-transition-old(root)
      ::view-transition-new(root)

Each pseudo-element has a different responsibility.

Pseudo-Element Purpose
::view-transition Contains all generated View Transition groups.
::view-transition-group() Controls the position and dimensions of a named group.
::view-transition-image-pair() Contains the old and new visual representations.
::view-transition-old() Represents the static snapshot of the previous visual state.
::view-transition-new() Represents the updated visual state.

The ::view-transition-old() pseudo-element is always a child of its corresponding ::view-transition-image-pair().

It does not have any child pseudo-elements.

If a transition group has no old visual state, its ::view-transition-old() pseudo-element is not generated.

Understanding the Old Visual State

The old visual state is the appearance of an element before a View Transition updates the document.

For example, consider a card with a blue background:

.card {
  background-color: #0066cc;
}
.card.updated {
  background-color: #333;
}

When the updated class is added during a View Transition, the browser can animate between the two appearances.

The old snapshot shows the original blue card.

The new representation shows the updated dark card.

The browser can display both states temporarily and animate between them.

The old snapshot is rendered as replaced content, similar to an image.

Its natural dimensions correspond to the size of the captured content.

Properties such as object-fit and object-position can control how the snapshot fits within its generated box.

Once the View Transition finishes, the old snapshot is removed.

The actual document remains in its updated state.

Targeting Named Transitions

The view-transition-name property allows individual elements to participate in separate View Transition groups.

For example:

.featured-card {
  view-transition-name: featured-card;
}

The old snapshot for this group can be styled using:

::view-transition-old(featured-card) {
  animation-duration: 500ms;
}

Different elements can use different transition names:

.site-logo {
  view-transition-name: site-logo;
}
.featured-card {
  view-transition-name: featured-card;
}

Their outgoing representations can be styled independently:

::view-transition-old(site-logo) {
  animation-duration: 250ms;
}
::view-transition-old(featured-card) {
  animation-duration: 600ms;
}

The logo uses a shorter animation, while the card uses a longer one.

Each rendered participating element must have a unique transition name within a document state.

The browser normally assigns the name root to the document's root element.

Use ::view-transition-old(root) to style the outgoing root snapshot.

Creating a Fade-Out Animation

A fade-out animation gradually hides the old visual state.

The following example defines a custom fade-out animation:

@keyframes fade-out-old {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
::view-transition-old(root) {
  animation: 500ms ease-in both fade-out-old;
}

The old root snapshot begins fully visible and gradually becomes transparent.

The animation shorthand specifies the duration, timing function, fill mode, and animation name.

The new visual state can be animated separately:

@keyframes fade-in-new {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
::view-transition-new(root) {
  animation: 500ms ease-out both fade-in-new;
}

When combined, the old state fades out while the new state fades in.

These animations replace the default animations for the selected representations.

For simple appearance changes, the browser's default cross-fade may be sufficient without additional CSS.

Creating a Slide-Out Animation

The old visual state can also move out of view using CSS transforms.

For example:

@keyframes slide-out-old {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(-30px);
  }
}
::view-transition-old(featured-card) {
  animation: 500ms ease-in both slide-out-old;
}

The old card snapshot moves 30 pixels to the left while fading out.

This can be useful for dismissing cards, closing panels, or changing displayed content.

The new visual state can use a complementary entrance animation:

@keyframes slide-in-new {
  from {
    opacity: 0;
    transform: translateX(30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
::view-transition-new(featured-card) {
  animation: 500ms ease-out both slide-in-new;
}

These animations can create a coordinated slide between the outgoing and incoming content.

The surrounding transition group may also animate its size and position automatically.

Custom transforms should be tested carefully when combined with browser-generated movement.

Example

The following example demonstrates a custom fade-out animation applied to the old visual state of a card.

Click the button to change the card's appearance and watch the previous state fade away.

HTML:

<div class="vt-old-demo" id="vt-old-demo">
  <div class="vt-old-card">
    <strong>Old Visual State</strong>
    <p>Watch the previous card appearance fade away.</p>
  </div>
  <button type="button" id="vt-old-toggle">Change Appearance</button>
</div>

CSS:

.vt-old-demo {
  max-width: 300px;
}
.vt-old-card {
  padding: 20px;
  border-radius: 6px;
  background-color: #0066cc;
  color: #fff;
  view-transition-name: vt-old-card;
}
.vt-old-card p {
  margin: 8px 0 0;
  color: inherit;
}
.vt-old-demo.is-alternate .vt-old-card {
  border-radius: 20px;
  background-color: #333;
}
.vt-old-demo button {
  margin-top: 12px;
  padding: 8px 14px;
  border: 0;
  background-color: #0066cc;
  color: #fff;
  cursor: pointer;
}
@keyframes vt-old-fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
@keyframes vt-old-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
::view-transition-old(vt-old-card) {
  animation: 600ms ease-in both vt-old-fade-out;
}
::view-transition-new(vt-old-card) {
  animation: 600ms ease-out both vt-old-fade-in;
}

JavaScript:

const demo = document.querySelector("#vt-old-demo");
const button = document.querySelector("#vt-old-toggle");
button.addEventListener("click", () => {
  const update = () => demo.classList.toggle("is-alternate");
  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (document.startViewTransition && !reduceMotion) {
    document.startViewTransition(update);
  } else {
    update();
  }
});
Browser Result
Old Visual State

Watch the previous card appearance fade away.

Try it: Click Change Appearance to switch between the blue and dark card styles.

In supporting browsers, the old card appearance fades out while the updated appearance fades in.

The ::view-transition-old(vt-old-card) rule controls the outgoing snapshot.

The ::view-transition-new(vt-old-card) rule controls the incoming representation.

Both animations last 600 milliseconds.

If View Transitions are unsupported or reduced motion is preferred, the card changes immediately.

Note: The old representation is a static snapshot. The original card element is updated during the transition, but its captured image remains unchanged until the transition finishes.

Animating Removed Elements

The ::view-transition-old() pseudo-element is particularly useful when an element is removed from the document.

If the element exists in the old state but not the new state, the browser can generate an old snapshot without a corresponding new representation.

For example:

.notification {
  view-transition-name: notification;
}

JavaScript can remove the notification during a View Transition:

const notification = document.querySelector(".notification");
if (notification) {
  const removeNotification = () => notification.remove();
  if (document.startViewTransition) {
    document.startViewTransition(removeNotification);
  } else {
    removeNotification();
  }
}

The browser can animate the old snapshot even after the actual notification element has been removed.

For example:

@keyframes notification-exit {
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}
::view-transition-old(notification):only-child {
  animation: 300ms ease-in both notification-exit;
}

The :only-child pseudo-class matches the old representation when no corresponding new representation exists in its image pair.

This allows an exit animation to be applied specifically to removed elements.

When an element exists in both states, the :only-child selector does not match.

This technique is useful for dismissible notifications, removable cards, and temporary interface elements.

::view-transition-old() vs. ::view-transition-new()

The ::view-transition-old() and ::view-transition-new() pseudo-elements represent the outgoing and incoming visual states of a transition.

Feature ::view-transition-old() ::view-transition-new()
Visual state Before the document update. After the document update.
Representation Static captured snapshot. Live representation of the updated state.
Typical animation Fade out or slide out. Fade in or slide in.
Parent ::view-transition-image-pair() ::view-transition-image-pair()
When generated When an old state exists. When a new state exists.

For example:

::view-transition-old(featured-card) {
  animation: 300ms ease-in both fade-out-old;
}
::view-transition-new(featured-card) {
  animation: 300ms ease-out both fade-in-new;
}

These rules use separate animations for the outgoing and incoming states.

The corresponding keyframes must also be defined.

When an element exists only in the old state, the browser omits the new representation.

When an element exists only in the new state, the browser omits the old representation.

Both pseudo-elements can be styled independently or grouped in a shared CSS selector.

Using View Transition Classes

The view-transition-class property allows multiple transition groups to share a styling identifier.

Unlike view-transition-name, a View Transition class does not need to be unique.

For example:

.card-one {
  view-transition-name: card-one;
  view-transition-class: cards;
}
.card-two {
  view-transition-name: card-two;
  view-transition-class: cards;
}

Each card has a unique transition name, but both share the class cards.

Their outgoing snapshots can be styled together:

::view-transition-old(.cards) {
  animation-duration: 500ms;
}

The dot before cards identifies a View Transition class selector.

A specific named group can receive a different animation:

::view-transition-old(card-one) {
  animation-duration: 700ms;
}

This allows common animation styling while preserving control over individual transition groups.

View Transition classes should be checked separately for browser compatibility because their support may differ from basic View Transitions.

Supported CSS Properties

The ::view-transition-old() pseudo-element behaves as replaced content and supports applicable CSS properties for animations, visual effects, and image positioning.

Property Purpose
animation Defines the animation applied to the old visual state.
animation-duration Controls how long the animation takes.
animation-timing-function Controls animation easing.
opacity Controls transparency of the outgoing snapshot.
transform Applies movement, scaling, or rotation.
transform-origin Sets the origin for transformations.
object-fit Controls how the captured image fits its box.
object-position Controls positioning of the captured image within its box.
filter Applies visual effects to the outgoing snapshot.
clip-path Clips the snapshot for custom exit effects.
mix-blend-mode Controls how the old representation blends with other content.

For example:

::view-transition-old(featured-card) {
  animation-duration: 500ms;
  object-fit: cover;
  object-position: center;
}

This customizes the outgoing animation duration and the snapshot's fitting behavior.

Changing properties used by browser-generated animations can alter or replace the default transition effects.

For most projects, animation duration, opacity, and transforms provide sufficient customization.

Limitations

The ::view-transition-old() pseudo-element has several limitations.

  • It exists only during an active View Transition.
  • It does not start a transition by itself.
  • It is generated only when a corresponding old visual state exists.
  • It is always a child of ::view-transition-image-pair().
  • It does not have child pseudo-elements.
  • Its content is a static snapshot rather than a live HTML element.
  • It does not permanently modify the original document.
  • Custom animations can override the browser's default fade-out behavior.
  • Changes to positioning, sizing, or blending can interfere with automatic transition rendering.
  • Cross-document transitions have additional browser and navigation requirements.

For example, the following CSS does not create a transition by itself:

::view-transition-old(featured-card) {
  animation-duration: 500ms;
}

A View Transition must be active, and the browser must generate an old visual representation for the named group.

Use view-transition-name and an appropriate transition mechanism to create the required conditions.

Accessibility Considerations

Outgoing animations can help users understand visual changes, but excessive movement or flashing can make interfaces uncomfortable or difficult to follow.

Follow these accessibility practices:

  • Respect the user's reduced-motion preference.
  • Avoid rapid flashing, excessive scaling, and unnecessary movement.
  • Keep exit animations short and predictable.
  • Maintain readable text and sufficient contrast during transitions.
  • Preserve keyboard focus when document content changes.
  • Do not use animations as the only indication that content has been removed.
  • Ensure the interface remains usable without View Transitions.
  • Test transitions at different zoom levels and viewport sizes.

For example, reduce animation durations when reduced motion is preferred:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01ms;
  }
}

For same-document transitions, JavaScript can skip the animation entirely:

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (document.startViewTransition && !reduceMotion) {
  document.startViewTransition(updatePage);
} else {
  updatePage();
}

This performs the update immediately when reduced motion is preferred or the API is unavailable.

Browser Support

The ::view-transition-old() pseudo-element has a Baseline Newly available status, with support across the latest major browsers since October 2025.

It is supported in current versions of Chrome, Edge, Firefox, Safari, and other browsers implementing CSS View Transitions.

Older browser versions may not support the feature.

Same-document and cross-document View Transitions have separate compatibility requirements.

To check whether the browser recognizes the selector:

@supports selector(::view-transition-old(root)) {
  ::view-transition-old(root) {
    animation-duration: 500ms;
  }
}

This applies the declaration only when the browser recognizes the selector.

It does not start a transition or guarantee that an old representation exists.

For same-document transitions, provide a fallback when the JavaScript API is unavailable:

if (document.startViewTransition) {
  document.startViewTransition(updatePage);
} else {
  updatePage();
}

For current compatibility information, visit Can I Use? and search for View Transitions API.

Check support for cross-document transitions and View Transition classes separately when using those features.

Specifications

The ::view-transition-old() pseudo-element is defined in the CSS View Transitions Module Level 1 .

The specification defines the pseudo-element as a static visual snapshot of the old document state during a View Transition.

It is rendered as replaced content within the corresponding ::view-transition-image-pair().

Related features include ::view-transition-new(), ::view-transition-group(), ::view-transition-image-pair(), the view-transition-name property, and the View Transitions API.