The View Transition API in 3 Examples
Photo by Colton Duke on Unsplash

The View Transition API in 3 Examples

How to animate between pages using the View Transition API.

3 min read


Smooth animations help make a UI feel polished and intuitive. Instead of abrupt changes between pages, adding graceful animations can really go a long way.

Adding these animations should be simple, but it hasn’t been easy on the web. Typically you've always had to use some amount of JavaScript and the end result was pretty complicated.

Fortunately, we now have the View Transition API. With View Transitions, you can create seamless animations between pages in your application or website.

In this article, you’ll learn the basics of the View Transition API with three CSS focused examples. In fact, this blog uses a variation of one of these examples!

Please note that this article focuses on cross document ("multi-page") transitions between basic HTML files, but view transitions can be done in single page applications as well.

As of the time of this writing, browser support for View Transitions is limited. Oddly enough, the demo in this article seems to work best in Safari 18.2.

Getting Started

The View Transition API introduces some new CSS pseudo elements and at-rules that can be used to manage the animation between pages.

The first step is to enable view transitions using the @view-transition at-rule.


@view-transition {
  navigation: auto;
}

This will opt the page into using a view transition, and because the browser has defaults for this transition, you’ll get a subtle fade effect without writing any other code!

Example 1 - Basic Fade

Let’s take what we did above a step further and control the fade speed.

We can do this just by adding some animation keyframes.


@keyframes old-out {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@keyframes new-in {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

Next we’ll use these keyframes along with the view-transition-old and view-transition-new pseudo elements.

These elements represent the old and new states of the view transition - the “old” being a static snapshot of the page before the transition, and the “new” being a live representation of the page after the transition.


&::view-transition-old(root) {
  animation-name: old-out;
  animation-duration: 1.5s;
}

&::view-transition-new(root) {
  animation-name: new-in;
  animation-duration: 1.5s;
}

We’re using “root” in the element selector here to target the root view transition just to keep things simple and because we only have one transition.

In other cases, you would use a view transition name instead of "root", like ::view-transition-old(card).

Optionally, instead of defining the animation-duration in multiple spots, you can use the ::view-transition-group element, which will apply to both the old and new.

Example 2 - Scroll Up

For the second example, we’ll add an animation that scrolls upwards between pages.

The code for this is very similar to the previous example.


@keyframes old-out {
  0% {
    transform: translateY(0);
    opacity: 1;
  }

  100% {
    transform: translateY(-100%);
    opacity: 0;
  }
}

@keyframes new-in {
  0% {
    transform: translateY(100%);
    opacity: 0;
  }

  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

The only difference in these keyframes is the addition of the transform property.

Finally add the old and new view transition pseudo elements.


&::view-transition-old(root) {
  animation-name: old-out;
  animation-duration: 1.6s;
}

&::view-transition-new(root) {
  animation-name: new-in;
  animation-duration: 1.6s;
  animation-delay: 1s;
}

Example 3 - Scale and Scroll

For the last example, we’ll add an animation that scales the visible page content slightly before moving horizontally to the next page and scaling back up.

Fortunately, we can continue using the same approach as the previous examples - defining keyframes and mapping them to the view transition elements.


@keyframes old-out {
  0% {
    scale: 1;
    transform: translateX(0%);
  }

  50% {
    scale: 0.8;
    transform: translateX(0%);
  }

  100% {
    scale: 0.8;
    transform: translateX(-200%);
  }
}

@keyframes new-in {
  0% {
    scale: 0.8;
    transform: translateX(200%);
  }

  50% {
    scale: 0.8;
    transform: translateX(0%);
  }

  100% {
    scale: 1;
    transform: translateX(0%);
  }
}

Next we’ll use a new psuedo element to apply a background to the animation while it’s occurring - view-transition-image-pair. The view-transition-image-pair element is basically just a container around the new and old elements, which works nicely for this particular effect.


::view-transition-image-pair(root) {
  background: linear-gradient(
    90deg,
    rgba(34, 193, 195, 1) 0%,
    rgba(253, 187, 45, 1) 100%
  );
}

Finally we have the old and new pseudo elements just like before.


::view-transition-group(root) {
  animation-duration: 2s;
}

&::view-transition-old(root) {
  animation-name: old-out;
}

&::view-transition-new(root) {
  animation-name: new-in;
  animation-delay: 1s;
}

As you can see, even for more advanced animations, the code required is not very involved and we used no JavaScript at all!

Browser Support

As mentioned above, browser support is currently limited for View Transitions, so be sure to check what you need to support before using this in production.

Depending on the animation, if a browser doesn't support View Transitions, the page navigation will occur normally.

If you need to provide a fallback, you can use the following at-rule.


@supports not (view-transition-name: none) {
  
}