Vue.js 3 Animating Components, Elements & Routes Tutorial

In this Vue tutorial we learn how to animate routes.

We cover how to use the transition component to animate routes and what to do with multiple root elements.

Lesson Project

If you want to follow along with the examples, you will need an app generated by the Vue CLI that includes the Vue Router .

We’ll also need the following view components.

  • /src/views/Home.vue
  • /src/views/About.vue

The project should look similar to the following.

Example: project
project-name/
├── src/
|   ├── views/
|   |   ├── Home.vue
|   |   └── About.vue
|   |
|   ├── main.js
|   └── App.vue

To keep things simple, we’ll set up the router directly in the /src/main.js file.

Example: src/main.js
import { createRouter, createWebHistory } from 'vue-router'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('./views/About.vue')
    }
  ]
})

app.use(router)
app.mount('#app')

The Home and About views will each contain a heading with some identifying text.

Example: src/views/Home.vue
<template>
  <h2>Home page</h2>
</template>
Example: src/views/About.vue
<template>
  <h2>About page</h2>
</template>

The root App component contains two router links to the different views as well as the router-view component to display the views. We also add some styling to help with the demonstration.

Example: src/App.vue
<template>
  <router-link to="/">Home</router-link> |
  <router-link to="/about">About</router-link>

  <router-view />
</template>

<style>
#app {text-align:center}
</style>

If we test the example in the browser, we should be able to switch between the two pages and see their headings.

How to animate a route with the transition component

Vue 3 changes the way we animate the transition between routes. Even though it might work, wrapping a router-view with the transition component isn’t supported anymore.

To demonstrate, let’s wrap the router-view in our example with a transition component and set up some simple animations.

Example: src/App.vue
<template>

  <router-link to="/">Home</router-link> |
  <router-link to="/about">About</router-link>

  <transition name="route" mode="out-in" appear>
    <router-view />
  </transition>

</template>

<style>
#app {text-align:center}

.route-enter-from {
  opacity: 0;
  transform: translateY(100px)
}
.route-enter-active,
.route-leave-active {
  transition: all .2s ease-out
}
.route-leave-to {
  opacity: 0;
  transform: translateY(100px)
}
</style>

If we run the example in the browser and switch between the pages, the animation works. But if we open the browser’s dev tools and take a look in the console, we’ll see a warning.

Output:
<router-view> can no longer be used directly
inside <transition> or <keep-alive>.

Use slot props instead

If we want to animate transitions between routes, we have to use the v-slot API and dynamic components .

To do this we change our router-view to an open-and-close tag that wraps around the transition component. Then we add a slot prop object with the component that will be rendered in the router-view .

Syntax: router-view v-slot
<router-view v-slot="{ Component }">
  <transition>

  </transition>
</router-view>

Once we have the component reference, we can create a dynamic component and bind the is prop to it to specify that we want to render the router-view component.

Syntax: router-view dynamic component
<router-view v-slot="{ Component }">
  <transition>
    <component :is="Component" />
  </transition>
</router-view>

To demonstrate, let’s change our example to use the v-slot API.

Example: src/App.vue
<template>

  <router-link to="/">Home</router-link> |
  <router-link to="/about">About</router-link>

  <router-view v-slot="{ Component }">
    <transition name="route" mode="out-in" appear>
      <component :is="Component" />
    </transition>
  </router-view>

</template>

<style>
#app {text-align:center}

.route-enter-from {
  opacity: 0;
  transform: translateY(100px)
}
.route-enter-active,
.route-leave-active {
  transition: all .2s ease-out
}
.route-leave-to {
  opacity: 0;
  transform: translateY(100px)
}
</style>

When we run the example in the browser, the animation still works but the warning is gone from the console.

Multiple root elements

Because of the Fragments API, we’re allowed to have multiple root elements in our template block instead of needing a wrapper element.

Example: fragments
<template>
  <h1>Heading</h1>
  <p>Multiple root elements allowed</p>
</template>

Unfortunately, this doesn’t apply to animations because CSS requires a single root to which the transitions can refer to.

To demonstrate, let’s change our example to have a second root element in the Home view.

Example: src/views/Home.vue
<template>
  <h2>Home page</h2>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
</template>

If we run the example in the browser and try to switch to another page, the view doesn’t load and Vue raises the following warning in the console.

Output:
Component inside <Transition> renders non-element root node that cannot be animated.

To fix this, all we have to do is add a wrapper element.

Example: src/views/Home.vue
<template>
  <div>
    <h2>Home page</h2>
    <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
  </div>
</template>

This time, our pages show and the warning in the console is gone.