CSS Transitions In Vuejs And Nuxtjs

About The Author

Front-end developer based in Lagos, Nigeria. He enjoys converting designs into code and building things for the web. More about Timi ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

Transitions are a nice way to remove, change, or update data in an application because their occurrence adds a nice effect and is good for the user experience. In this tutorial, Timi Omoyeni will look at the different ways to apply transitions in both Vue.js and Nuxt.js applications. Using these transitions in your applications and websites create a better visual experience and sometimes draws and holds the user’s attention while a piece of information is being introduced to or leaving the screen.

Transitions are a module of CSS that lets you create gradual transitions between the values of specific CSS properties. The behavior of these transitions can be controlled by specifying their timing function, duration, and other attributes. Using these transitions in your applications and websites create a better visual experience and sometimes draws and holds the user’s attention while a piece of information is being introduced to or leaving the screen. According to Can I Use, transitions are supported by most browsers, although there are some minor issues with Internet Explorer and Safari.

Data on support for the css-transitions feature across the major browsers from caniuse.com
Source: caniuse.com. (Large preview)

Vue.js is an open-source JavaScript framework for building client-facing web applications and single-page applications (SPA). One of the features of this framework is the ability to add transitions to an app seamlessly, to switch between either pages or components, and we’re going to look at how to do that in this tutorial.

Nuxt.js is also a JavaScript framework, built on top of Vue.js (and often referred to as a framework of a framework), for building server-side web applications, static-generated websites, as well as SPAs. It works the same as Vue.js, so if you know Vue.js, you shouldn’t have many issues getting started with Nuxt.js. Nuxt.js comes with two properties for adding transitions to an app, and we’re going to cover those as well in this tutorial.

This tutorial requires basic knowledge of either Vue.js or Nuxt.js. All of the code snippets in this tutorial can be found on GitHub.

What Is A Transition?

According to the Oxford Dictionary, a transition can be defined as:

“A passage in a piece of writing that smoothly connects two topics or sections to each other.

The process or a period of changing from one state or condition to another.”

In terms of physics, a transition is defined thus:

“A change of an atom, nucleus, electron, etc. from one quantum state to another, with emission or absorption of radiation.”

From these definitions, we get an idea on what a transition is. The definitions all involve two different things or states. In terms of code, a transition is not so different.

What Is A CSS Transition?

According to Mozilla’s web documentation:

“CSS Transitions is a module of CSS that lets you create gradual transitions between the values of specific CSS properties. The behavior of these transitions can be controlled by specifying their timing function, duration, and other attributes.”

This means we can define a CSS transition as: the change in the CSS property of one or more elements from one value to another.

The CSS transition property enables us to add a transition effect to any valid element. It consists of up to four other properties (five, if we count the transition property itself) that can be used individually or combined as a shorthand. Each property has a different function.

transition-property

The transition-property accepts the name of the CSS property that we want to watch out for changes on and whose change process we want to transition. It looks like this:

.btn {
  width: 200px;
  height: 50px;
  transition-property: width;
  background-color: red;
  color: #fff;
  border: 0;
}

But this property does not do anything without the next property.

transition-duration

The transition-duration property specifies the time that the change of the element(s) in the transition-property should go on for. This property is required in order for the transition to work. If it is not set (with a value greater than 0s), then the default value of 0s would mean it would not run. So, let’s set a duration for this transition:

.btn {
  width: 200px;
  transition-property: width;
  transition-duration: 2s;
  background-color: red;
  color: #fff;
  border: 0;
}

Here, we have an element with a class name of btn that has a width of 200px. We are using both the transition-property and the transition-duration properties here. What this means is, “Hey, CSS, watch out for when the width property changes, and when this happens, let the effect take 2s to change.”

So, if we have a button with a class name of btn, then the index.html file would look like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS Transitions</title>
    <link rel="stylesheet" href="./assets/styles.css">
</head>
<body>
    <Section>
        <h1>Hi CSS Transitions</h1>
        <button class="btn">Hover on me to see a cool transition</button>
    </Section>
</body>
</html>

Here, we have an HTML file that contains a button with a class that has transition-property and transition-duration properties watching out for changes to the element’s width.

One thing to note is that, in order for the transition on our button to work, we have to actually change the width of that element, either by manually adjusting the width with the developer tools in the browser, by using one of the CSS pseudo-classes, or by using JavaScript. For the purpose of this tutorial, we’re going to use the CSS pseudo-class :hover to change the width of the button:

// existing styles
.btn:hover {
  width: 300px;
}

Now, if we hover over this button, we should see the width of the button gradually increase over the set time, which is 2s.

See the Pen transition-property and transition-duration by Timi Omoyeni (@timibadass) on CodePen.

transition-timing-function

The transition-timing-function property determines the speed at which the transition effect occurs. Five values are available for this property:

  • ease
    This (the default) specifies a transition effect that starts slowly, then gets fast, then ends slowly.
  • linear
    This specifies a transition effect with the same speed from start to end.
  • ease-in
    This specifies a transition effect with a slow start.
  • ease-out
    This specifies a transition effect with a slow end.
  • ease-in-out
    This specifies a transition effect with a slow start and end.
  • cubic-bezier(n,n,n,n)
    This lets you define your own values in a cubic-bezier function.

So, if we add ease-in to our button, we should notice the speed at which the width and height change, compared to the speed at which the button returns to its normal state. Here is our updated styles.css sheet:

.btn {
  width: 200px;
  height: 50px;
  transition-property: width;
  transition-duration: 2s;
  transition-timing-function: ease-in;
  background-color: red;
  color: #fff;
  border: 0;
}

If we want a more dramatic speed effect or the freedom to set a specific speed effect, we can use cubic-bezier(n,n,n,n):

btn {
    width: 200px;
    height: 50px;
    transition-property: width;
    transition-duration: 2s;
    transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
    background-color: red;
    color: #fff;
    border: 0;
}

See the Pen transition-timing-function by Timi Omoyeni (@timibadass) on CodePen.

One interesting thing about this value is that you can edit it directly in the browser using the developer tools.

cubic bezier in dev tools.
Cubic bezier in the browser’s developer tools. (Large preview)

If you click on the highlighted part of your developer tools, you’ll get an interface to modify the cubic-bezier options:

cubic bezier interface highlighted in yellow.
Cubic bezier interface highlighted in yellow. (Large preview)

As you move the two points around, the values of (n,n,n,n) change, and you’ll see a representation (highlighted in red) of how the speed effect will appear. This can be very useful when you have a specific speed effect in mind.

transition-delay

The transition-delay property sets how long (in seconds) the transition has to wait before its effect starts to occur. This time is different from the transition-duration property, which specifies how long the transition effect will take place.

.btn {
  width: 200px;
  height: 50px;
  transition-property: width;
  transition-duration: 2s;
  transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
  transition-delay: 5s;
  background-color: red;
  color: #fff;
  border: 0;
}

If you try this in the browser, you will notice a delay before the element’s width starts to change. This is because of the transition-delay property and value that we have set.

See the Pen transition-delay by Timi Omoyeni (@timibadass) on CodePen.

Shorthand Property

The individual transition properties can be tedious to use. For this reason, we have the shorthand property: transition. It accepts all of the properties in a defined order:

{
  transition: a b c d;
}

Here, the letters correspond as follows:

  • a: transition-property
  • b: transition-duration
  • c: transition-timing-function
  • d: transition-delay

We can refactor our existing transition to work using this shorthand property:

// from
.btn {
  width: 200px;
  height: 50px;
  transition-property: width;
  transition-duration: 2s;
  transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
  transition-delay: 1s;
  background-color: red;
  color: #fff;
  border: 0;
}

// to
.btn {
  width: 200px;
  height: 50px;
  transition: width 2s cubic-bezier(0.075, 0.82, 0.165, 1) 1s;
  background-color: red;
  color: #fff;
  border: 0;
}

If we try this code in the browser, we will get the same transition effect that we got when we used the individual properties.

See the Pen using shorthand property by Timi Omoyeni (@timibadass) on CodePen.

Transitions In Vue.js

Vue.js comes with two different ways to add transitions to an application. This doesn’t mean we cannot use transitions the CSS way. It just means that Vue.js’ developers have built on top of CSS to make it easier to use transitions. Let’s look at them one by one.

Transitioning Individual Elements And Components

One way we can use transitions in Vue.js is by wrapping the transition component around an element or component, using any of the following:

  • conditional rendering ( using v-if),
  • conditional display (using v-show),
  • dynamic components,
  • component root nodes.

When we are developing an application, there are instances when we want to display data to the user depending on a value (such as a boolean). Here’s an example of how this works, taken from the index.vue file:

<template>
  <div>
    <p v-if="show">Now you see me!</p>
    <p v-else>Now you don't!</p>
    <button @click="show = !show">click me</button>
  </div>
</template>
<script>
export default {
 data() {
   return {
     show: true
   }
 }
}
</script>
<style>
</style>

We have added two paragraphs to this page that appear depending on the value of show. We also have a button that changes the value of show when clicked. We’ll add this page to our App.vue file by importing it like so:

<template>
  <div id="app">
    <Index />
  </div>
</template>
<script>
import Index from "./components/index.vue";
export default {
  name: 'App',
  components: {
    Index
  }
}
</script>

If we open the browser, we should see our paragraph and button:

vue landing page when show is true.
Vue.js landing page in default state. (Large preview)

Right now, clicking the button changes only the value of show, which causes the visible text to change:

landing page when show is false.
Landing page when button is clicked. (Large preview)

Adding a transition to this paragraph can be done by wrapping both paragraphs in the transition component. This component accepts a name prop, which is very important for the transition to work.

<template>
  <div>
    <transition name="fade">
      <p v-if="show">Now you see me!</p>
      <p v-else>Now you don't!</p>
    </transition>
    <button @click="show = !show">click me</button>
  </div>
</template>

This name tells Vue.js which transition to apply to the elements or components inside this transition component. At this point, if we click on the button, we would still not notice any transition because we have yet to add the configuration for our transition in the form of CSS classes.

One thing to note is that, when using a transition between two elements of the same tag, we need to specify a key attribute on each element in order for the transition to occur.

<template>
  <div>
    <transition name="fade">
      <p v-if="show" key="visible">Now you see me!</p>
      <p v-else key="notVisible">Now you don't!</p>
    </transition>
    <button @click="show = !show">click me</button>
  </div>
</template>

Vue.js has six transition classes that are applied to the elements or components inside the transition component, and each of these classes represents a state in the transition process. We’re going to look at only a few of them.

v-enter

The v-enter class represents the “starting state for enter”. This is the point at which a condition (v-if or v-else) has been met and the element is about to be made visible. At this point, the class has been added to the element, and it is removed once the element has been added. The name prop (in this case, fade) attached to the transition component is prefixed to this class name, but without the v. This v can be used by default if name is not provided. Thus, we can add this class to our index.vue file:

<style>
p {
  color: green;
}

.fade-enter{
  color: red;
  transform: translateY(20px);
}
</style>

First, we add a color of green to all of the paragraphs on the page. Then, we add our first transition class, fade-name. Inside this class, we change the color to red, and we make use of the transform and translateY property to move the paragraph by 20px along the y-axis (vertically). If we try clicking on the button again, we will notice that very little or no transition takes place during the switch because we need to add this next class we’ll look at.

v-enter-active

The v-enter-active class represents the “whole entering” state of a transitioning element. It means that this class is added just before the element is inserted or becomes visible, and it is removed when the transition has ended. This class is important for v-enter to work because it can be used to add the CSS transition property to the class, together with its properties (transition-property, transition-duration, transition-timing-function, and transition-delay), some of which are needed in order for the transition effect to work. Let’s add this class to our app and see what happens:

.fade-enter-active {
  transition: transform .3s cubic-bezier(1.0, 0.5, 0.8, 1.0), color .5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}

See the Pen vue transition enter state by Timi Omoyeni (@timibadass) on CodePen.

Now, if we click on the button, we will notice the transition of the color and the position of each of the texts as they come into view. But the transition from visible to hidden isn’t smooth enough because no transition is happening.

v-leave-active

The v-leave-active class represents the entire state in which an element changes from visible to hidden. This means that this class is applied from the moment an element starts to leave the page, and it is removed once the transition ends. This class is important in order for a leave transition to be applied because it takes in the CSS transition property, which also takes in other transition properties. Let’s add this to our app and see what happens:

.fade-leave-active {
  transition: transform 1s cubic-bezier(1.0, 0.5, 0.8, 1.0), color 1s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}

See the Pen vue transition leave active state by Timi Omoyeni (@timibadass) on CodePen.

When we click on the button now, we will notice that the element that should leave waits for approximately 2 seconds before disappearing. This is because Vue.js is expecting the next class with this transition to be added in order for it to take effect.

v-leave-to

The v-leave-to transition represents the “leaving” state, meaning the point at which an element starts to leave and its transition is triggered. It is added one frame after a leaving transition is triggered, and removed when the transition or animation finishes. Let’s add this class to our app and see what happens:

.fade-leave-to {
  transform: translateX(100px);
  color: cyan;
}

Clicking on the button, we will notice that each element that is leaving is sliding to the right as the color changes.

See the Pen vue transition leave to state by Timi Omoyeni (@timibadass) on CodePen.

Now that we understand how transitions work in Vue.js, here’s an image that brings it all together:

classification of vue transition classes
Classification of Vue.js transition classes. (Large preview)

Finally, notice the not-so-smooth transition that occurs during the enter and leave states of elements that are transitioning. This is because Vue.js’ transitions occur simultaneously. Vue.js has a mode prop that helps us achieve a very smooth transition process. This prop accepts one of the following values:

  • in-out
    The new element transitions in first, and then, when it’s complete, the current element transitions out.
  • out-in
    The current element transitions out first, and then, when complete, the new element transitions in.

If we add this mode to our index.vue file and try again, we should see a better transition:

<template>
  <div>
    <transition name="fade" appear mode="out-in">
      <p v-if="show" key="visible">Now you see me!</p>
      <p v-else key="notVisible">Now you don't!</p>
    </transition>
    <button @click="transitionMe">click me</button>
  </div>
</template>

Now, if we click on the button, we will notice that one element leaves before another enters. This is a result of the mode we have selected for this transition. If we try the other mode, we will get a different behavior.

See the Pen vue transition with mode by Timi Omoyeni (@timibadass) on CodePen.

List Transitions

If you ever try adding transitions to more than one element at a time using the transition component, an error will get printed to the console:

Vue transition error printed in console.
Vue.js error printed in console. (Large preview)

This is because the transition component is not meant to render more than one element at a time. If we want to transition two or more elements at a time or render a list (using v-for), we make use of the transition-group component. This component also accepts a name prop, but it has some differences from the transition component, including the following:

  • A key attribute is required for each element inside this component.
  • There is no need for the mode prop because more than one element would be rendered at a time.
  • A span element is rendered by default, but it can be modified by specifying a tag prop when defining the transition-group component. Let’s look at an example (in our listTransition.vue file):
    <template>
  <div>
    <h1>List/Group Transition</h1>
    <ul>
      <li v-for="user in users" :key="user.id">
        {{user.name}}
        <button>Remove</button>
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  data() {
    return {
      users: [
        {
          name: "Vuejs",
          id: 1
        },
        {
          name: "Vuex",
          id: 2
        },
        {
          name: "Router",
          id: 3
        }
      ]
    };
  }
};
</script>
<style>
</style>

Here, we have an array of users, which we loop through using v-for, displaying the name in our template section. In order to be able to view this list, we need to import this component into the App.vue page:

<template>
  <div id="app">
    <Index />
    <listTransition />
  </div>
</template>
<script>
import Index from "./components/index.vue";
import listTransition from "./components/listTransition.vue";
export default {
  name: "App",
  components: {
    Index,
    listTransition
  }
};
</script>

Note that when using the transition-group component, instead of wrapping our list with a ul tag (or any tag we have in mind), we wrap it around the transition-group component and add the tag to the tag prop, like this:

<template>
  <div>
    <h1>List/Group Transition</h1>
    <transition-group name="slide-fade" tag='ul'>
      <li v-for="user in users" :key="user.id">
        {{user.name}}
        <button>Remove</button>
      </li>
    </transition-group>
  </div>
</template>
<script>
export default {
  data() {
    return {
      users: [
        {
          name: "Vuejs",
          id: 1
        },
        {
          name: "Vuex",
          id: 2
        },
        {
          name: "Router",
          id: 3
        }
      ]
    };
  }
};
</script>
<style>
.slide-fade-enter-active {
  transition: transform 0.3s cubic-bezier(1, 0.5, 0.8, 1),
    color 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-leave-active {
  transition: transform 1s cubic-bezier(1, 0.5, 0.8, 1),
    color 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter {
  color: mediumblue;
  transform: translateY(20px);
}
.slide-fade-leave-to {
  transform: translateX(100px);
  color: cyan;
}
</style>

Here, we have replaced the ul tag with the transition-group component, and added the ul as the tag prop value. If we inspect the updated page in the developer tools, we will see that the list is being wrapped in the element that we specified in the tag prop (that is, ul).

ul tag highlighted in dev tools.
The ul tag highlighted in the developer tools. (Large preview)

We have also added a transition name prop with a value of slide-fade to this component, with style rules below in the style section that follow this naming convention. For this to work, we need to add the following lines of code to our file:

<template>
  <div>
    <h1>List/Group Transition</h1>
    <transition-group name="slide-fade" tag="ul">
      <li v-for="user in users" :key="user.id">
        {{user.name}}
        <button @click="removeUser(user.id)">Remove</button>
      </li>
    </transition-group>
  </div>
</template>
<script>
export default {
  // ...
  methods: {
    removeUser(id) {
      let users = this.users.filter(user => user.id !== id);
      this.users = users;
    }
  }
};
</script>
  

In the template section, we add a click event to each button in the loop and pass the user.id to the removeUser method attached to this click event. We then create this function in the script section of our file. This function accepts an id as argument. Then, we run through our existing users and filter out the user with the id passed into this function. When this is done, we save our new array of users to the data of our page.

At this point, if you click on any of the buttons for the users, a transition effect will be applied as the user is being removed from the list.

See the Pen Vue list transition by Timi Omoyeni (@timibadass) on CodePen.

Transitions In Nuxt.js:

Adding transitions to a Nuxt.js application is quite different from how you might be used to in Vue.js. In Nuxt.js, the transition component is automatically added to the application for you. All you need to do is one of the following.

Add It To Individual Page Component

Nuxt.js allows us to add transitions to an individual page component seamlessly. This transition is applied while the user is navigating to this page. All we have to do is add a transition property to the script section of the component. This property can be a string, a function, or an object. Some of the properties it accepts are:

  • name,
  • mode,
  • css.

Like Vue.js, Nuxt.js has a default name that gets assigned to a transition class if no name is provided, and it is called page. Let’s see how this works when we add it to our application in transition.vue:

<template>
  <div>
    <p>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Labore libero
      odio, asperiores debitis harum ipsa neque cum nulla incidunt explicabo ut
      eaque placeat qui, quis voluptas. Aut necessitatibus aliquam veritatis.
    </p>
    <nuxt-link to="/">home</nuxt-link>
  </div>
</template>
<script>
export default {
  transition: {
    name: "fade",
    mode: "out-in"
  },
  data() {
    return {
      show: true
    };
  }
};
</script>
<style>
p {
  color: green;
}
.fade-enter-active {
  transition: transform 0.3s cubic-bezier(1, 0.5, 0.8, 1),
    color 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}
.fade-leave-active {
  transition: transform 1s cubic-bezier(1, 0.5, 0.8, 1),
    color 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.fade-enter {
  color: mediumblue;
  transform: translateY(20px);
}
.fade-leave-to {
  transform: translateX(100px);
  color: cyan;
}
</style>

On this page, we’ve displayed “lorem ipsum” in the template section. We’ve also added the transition property, to which we have passed an object whose name is set to fade and whose mode is set to out-in. Finally, in the style section, we’ve added some styles that control the transition as the user navigates between this page and another.

In order for this transition to work, we have to navigate to /transition, but we would not notice any transition if we manually enter this route in our browser. So, let’s add a link to this page on the index.vue page.

<template>
  <div class="container">
    <div>
      // ..
      <nuxt-link to="/transition">next page</nuxt-link>
    </div>
  </div>
</template>

Now, if we click the link on either of the two pages, we will notice a sliding transition as the browser moves to and from the /transition route.

pageTransition

Adding transitions to individual pages can be challenging if we want to add them to all of the pages in the application. That’s where pageTransition comes in. This property allows us to add a general configuration for all of our pages in the nuxt.config.js file. This property accepts both a string and object as an option. Let’s see how that works in nuxt.config.js:

export default {
    // ...
    /*
     ** Global CSS
     */
    css: [
        '~/assets/transition.css'
    ],
    pageTransition: {
        name: "fade",
        mode: "out-in"
    },
}

Here, we’ve added the link to a CSS file, which we’ll create shortly. We have also added the pageTransition property to the file, along with with its configuration. Now, let’s create our CSS file, transition.css, and add the following styles to it:

.fade-enter-active {
    transition: transform 0.3s cubic-bezier(1, 0.5, 0.8, 1), color 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}
.fade-leave-active {
    transition: transform 1s cubic-bezier(1, 1, 1, 1), color 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.fade-enter {
    color: mediumblue;
    transform: translateY(20px);
}
.fade-leave-to {
    transform: translate3d(-500px, -300px 400px);
    color: cyan;
}

We’ve added the classes and styles that will be applied to the transition between one route and the other. If we get rid of the transition configuration from the transition.vue page and try to navigate between the two pages, we will get a transition effect.

layoutTransition

The layoutTransition property enables us to apply transitions based on the layout that the page is on. It works the same way as pageTranslation, except that it works based on layout. The default transition name is layout. Here’s an example of how it works, in nuxt.config.js:

export default {
    // ...
    /*
     ** Global CSS
     */
    css: [
        '~/assets/transition.css'
    ],
    layoutTransition: {
        name: "fade",
        mode: "out-in"
    },
}

Note that fade has to be the name of the layout in order for the transition to work with its layout. Let’s create this new layout in newLayout.vue to see what I mean:

<template>
  <!-- Your template -->
  <div>
    <h1>new layout</h1>
  </div>
</template>
<script>
export default {
  layout: "blog"
  // page component definitions
};
</script>

Conclusion

We have learned about CSS transitions and how to create them using the transition properties individually (transition-property, transition-duration, transition-timing-function, and transition-delay) and using the shorthand transition property. We have also covered how to apply these transitions in both Vue.js and Nuxt.js. But that’s not all. Vue.js has more ways for us to apply transitions in an application:

Further Reading

Smashing Editorial (ks, ra, yk, il, mrn)