Go back

Enhance React Animations: A Guide to the View Transitions API

I recreated the Feedback component from this tweet by Emil Kowalski without using framer motion.

Animations are made with view transitions api so it's not supported in all browsers yet.

Let's break down parts of the component to understand how it works.

Initial Component

First we need to create the component that will change the state and conditionally render the button, form or success message.


export function FeedbackInitialButton() {
  const [state, setState] = useState<"initial" | "open" | "success">("initial");

  function nextState() {
    setState((state) => {
      if (state === "initial") return "open";
      if (state === "open") return "success";
      return "initial";
    });
  }
  return (
    <div >
      {state === "initial" && <InitialButton nextState={nextState} />}
      {state === "open" && <FeedbackForm nextState={nextState} />}
      {state === "success" && <SuccessAlert nextState={nextState} />}
    </div>
  );
}

Adding Default Transitions

Then we add the transitions to the component using the view transitions API.

To add the transitions we need to call document.startViewTransition and inside it make the changes to the state. The changes are made inside a flushSync to make sure the changes are applied synchronously.

I created a helper function to wrap the transition logic:

function makeTransition(transition: () => void) {
  // Check if the browser supports the view transitions API
  // if not, just call the transition
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      flushSync(() => {
        transition();
      });
    });
  } else {
    transition();
  }
}

Then we can use it to wrap the state changes:

function nextState() {
  makeTransition(() => {
    setState((state) => {
      if (state === "initial") return "open";
      if (state === "open") return "success";
      return "initial";
    });
  });
}

This component looks great, but it's only animating a crossfade between states. We can make it more complex.

More Transitions

Grow Animation

We can animate parts of the component separately. For example, by setting the "view-transition-name" attribute to the same value in both the button and the form background, the button will "grow" into the form.

In this example, I put the view transition name in the button style prop, but you can also use a CSS class.

<button
  onClick={nextState}
  style={{
    viewTransitionName: "bg-grow",
  }}
>
  Open
</button>

And in the form component:

<div
  className="bg-gray-100"
  style={{
    viewTransitionName: "bg-grow",
  }}
>
  <div className="bg-white">
    <form>
    /* rest of the form */
    </form>
  </div>
</div>

Text Animation

View transitions allow us to animate parts of the component separately. We can use this to animate the text in the button and the background separately.

I put the button text in a span so I can animate it separately from the button.

<button
  style={{
    viewTransitionName: "bg-grow",
  }}
>
  <span
    style={{
      viewTransitionName: "text",
    }}
  >
    Feedback
  </span>
</button>

I also put a span as a placeholder in the form to animate the text separately.

<div
  className="bg-gray-100"
  style={{
    viewTransitionName: "bg-grow",
  }}
>
  <div className="bg-white">
    <form>
      <span
        style={{
          viewTransitionName: "text",
        }}
        aria-hidden
      >
        Feedback
      </span>
      /* rest of the form */
  </div>
</div>

The Great Thing About View Transitions

At this point, you can see how powerful view transitions are and we didn't even start using the pseudo-elements of the view transitions API that allow us to animate things like the blur in the success state of the component.

I hope this example helps you understand how to use the view transitions API with React components. If you have any questions, feel free to contact me.

Other Resources

Malcom post on view transitions with react

MDN docs on view transitions

Emil Kowalski (creator of the original component)