CSS Transitions

CSS transitions are a CSS-based way—as opposed to a JavaScript way—to update the value of a CSS property over a specified duration. Given a start value and an end value, the browser will interpolate in-between values over the course of the transition. They’re great for simple effects where you don’t mind giving up control over how the animation progresses.

In my own work, I sometimes use transitions for :hover states. I also use them when revealing or concealing content, such as showing an off-screen menu. You could create animations for such effects, but animations are generally more verbose, as you’ll see later in the chapter.

We can’t transition every property. We can only use transitions with properties that accept interpolatable values. Interpolation is a method of calculating values that fall within a range. These values are typically numeric unit values such as lengths, percentages, or colors. That means we can’t transition between visibility: visible and visibility: hidden , or display: block and display: none . Nor can we transition to or from auto values.

1. Creating Your First Transition

In this example, we’ll make our link color transition from blue to pink when users move their mouse over it, and back to blue when users moves their mouse off it.

Here’s our bare-bones HTML:

<!DOCTYPE html>

<html lang=”en-US”>


<link rel=”stylesheet” href=”style.css”>



<p>Mouse over <a href=”https://sitepoint.com/”>this link</a>to see the transition effect.</p>



This gives us the page shown below.

Now let’s add the following CSS to our style.css :

a {

transition: 1s;


a:link {

color: #309;


a:hover {

color: #f0c;


This is the bare minimum CSS required for a transition to work: a start value ( color: #309 ), an end value ( color: #f0c ), and a transition duration ( transition: 1s; ). When you mouse over the link, you’ll see a gradual transition from blue to hot pink, as illustrated below.

Transitions need to be triggered by some kind of event. Often, this is a user interaction. We might transition between colors when entering and leaving a :hover state, as we’ve done here. But we can also trigger a transition by adding or removing a class name using JavaScript. In the following example, we modify an element’s classList attribute to do just that:

const btn = document.querySelector( ‘button’ );

const clickHandler = () => {

document.body.classList.toggle( ‘change’ );


btn.addEventListener( ‘click’, clickHandler );

In the code, we’ve first defined a variable named btn . If you’re unfamiliar with programming, a variable is simply a bucket of sorts that holds a value. We can then use the variable anywhere we need that value.

The value of btn is our button element, as returned by document.querySeLector(‘button’) . The document.querySelector() method is defined by the Selectors API4 specification. It accepts any CSS selector as its argument, and returns the first item that matches. It’s a way to select elements using JavaScript.

Next, we’ve defined a clickHandler function. This will be the event listener for our click event. Finally, we’ve added the event listener to btn using addEventListener . The addEventListener method is part of the Document Object Model. It allows us to define a function that’s invoked when a particular event occurs.

The magic happens within the cLickHandLer function. Here we’ve used the ELement.cLassList.toggLe() method to add or remove the change class from the <body> element ( document.body ). This is what triggers our transition. The cLassList property is part of the Document Object Model API. It provides a handful of methods for manipulating the

Now let’s look at our CSS. It’s only a few lines long:

body {

background: #fcf;

transition: 5s;


.change {

background: #0cf;


Here, we’ve defined a starting background color for our <body> element, and a transition. We’ve also defined a .change class, which has a different value for background . When our event handler runs, it adds the change class to our <body> element. This triggers a transition from the original background color to the one defined in the .change declaration block, as shown below.

If you want a transition to work in both directions—for example, when the class is both added and removed—you should add it to whichever declaration block is your start state. We’ve done that here by including the transition property in the body declaration block. If we moved the transition to the change class, our transition would only work when change was added to our <body> element, but not when it was removed.

So far, we’ve used the transition shorthand property. It’s a condensed way of specifying four “longhand” properties, which are listed in the table below.

Each longhand property has an initial value. The browser uses the initial value for the property, unless you explicitly set its value. For example, the initial value of transition-property is all (all properties), and the initial value of transition-timing-function is ease . When we set a transition duration-such as transition: 1s —the values for transition-property and transition-timing-function are implied. This is why we can get away with setting the transition property and nothing else.

2. Using the transition Property

As we’ve already seen in the previous examples, time units are one acceptable value for the transition property. The CSS Values and Units Module Level 37 specification defines two kinds of time units for use with transitions and animations: s for seconds, and ms for milliseconds. We can also collapse values for transition-timing-function , transition- delay , and transition-property into this shorthand transition property:

body {

background: red;

transition: background 500ms linear 1s;


Here, we’ve told the browser to transition the background property. The duration will last 500 milliseconds (which we could also write as .5s ). It will use the Linear timing function (discussed later in this chapter), and the start of the transition will be delayed by one second. It’s a compact version of the following CSS:

body {

background: red;

transition–property: background;

transition–duration: 500ms;

transition–timing–function: linear;

transition–delay: 1s;


Order matters somewhat when using the transition shorthand property. The first value that can be interpreted as a time will become the transition duration no matter where it sits in the value string. The second time value will determine the transition delay. In other words, we could reorder the values in our transition property like so:

body {

background: red;

transition: 500ms 1s background linear;


Here, our transition duration will be 500ms with a one-second delay.

Using the transition property is the most concise way to define a transition. However, there may be cases in which you want to define a global transition effect (for example, transition: 500ms ease ) in one part of your CSS, and limit it to specific CSS properties (for example, transition-property: color ) in another. This is where the longhand properties are useful.

3. Transition Durations and Delays

The transition-duration property sets the duration of the transition, or how long it takes to complete. The transition-delay property determines how much time should elapse before the transition begins. Both properties accept time units as a value. These can be seconds or milliseconds: 1s , 2.5s , and 200ms are all valid values.

Both transition-duration and transition-delay have an initial value of 0s , or zero seconds. For transition-duration , this means there will be no gradual transition between the start and end states. For transition-delay , this means the transition will occur immediately.

With transition-duration , you must use values greater than zero, such as .5s or 2500ms . Negative values will be treated like a value of 0s , and the transition will fail to execute, as illustrated below.

However, negative values are valid for transition-delay . Positive transition-delay values shift the start of the animation by the specified amount of time. Negative values, however, offset the beginning of the transition, as seen above. Using transition-duration: 2s; transition-delay: -1s will cause the transition to jump one second into the play cycle before continuing. Using a negative transition-delay value can create a snappier transition experience by shortening its perceived duration.

4. Timing Functions

We can also shape transition effects using the transition-timing-function property. Timing functions are formulas of sorts that determine how the in-between values of a transition are calculated. Which timing function you use will depend on what kind of transition effect you’d like to achieve: a stepped transition or a smooth, gradual one.

5. Stepped Transitions

With stepped transitions, the play cycle is divided into intervals of equal value and duration. We can set how many intervals a transition should have using the steps timing function.

Let’s revisit our background color example from earlier in this chapter. Instead of using the default ease timing function, we’ll instead use the steps function to create a five-step transition. Our revised CSS looks like this:

body {

background: #f0f;

transition: 5s steps(5);


.change {

background: #0cf;


Rather than a smooth, gradual shift between colors, this transition cycles through five distinct color states.

There are also two keywords we can use to create stepped animations: step-start and step-end . These are equivalent to steps(1, start) and steps(1, end) . With these keywords (or their step function equivalents), you’ll see one transition step between the starting and ending values.

6. Smooth Transitions

Smooth transitions use the cubic-bezier function to interpolate values. Understanding how this function works involves a bit of math, along with some handwaving and magic. Read Pomax’s “A Primer on Bezier Curves if you’re interested in the intimate details. What follows is a simplified explanation.

The cubic Bezier function is based on the cubic Bezier curve. A Bezier curve consists of a start point and an end point, and one or more control points that affect the shape of the curve. A cubic Bezier curve always has two of these control points, which can be seen below. Curves are drawn from the start point to the end point, towards the control points.

7-5. A cubic Bézier curve, where the filled circles are the control points

The arguments passed to the cubic-bezier function represent the coordinates of those control points: x1, y1, x2, y2. But there’s a constraint on these points: X values (the first and third parameters) must fall between 0 and 1 . Y values (the second and fourth parameters) can exceed this range in either direction. In other words, cubic-bezier(0, 1.02, 1, 0) and cubic-bezier(0, 1.08, .98, -0.58) are valid values, but cubic-bezier(2, 1.02, -1, 0) is not.

Graphs are the best way to illustrate how cubic-bezier works. The X-axis is a function of the transition’s duration, as can be seen in the image below, which shows a graph of cubic- bezier(0.42, 0, 1, 1) . The Y-axis is a function of the value of the property that’s being transitioned. The outputs for these function determine the values of the property at a particular point in the transition. Changes in the graph match the changes in speed over the course of a transition. The image below shows a graph of cubic-bezier(0.42, 0, 1, 1) .

In most cases, it’s easier to use a timing function keyword. We mentioned step-start and step-end in the previous section, but there are five more keywords, each of which is an alias for cubic-bezier values. They’re listed in the following table:

7. Transitioning Multiple Properties

It’s possible to transition multiple properties of a single element using a transition list. Let’s look at an example:

div {

background: #E91E63;

height: 200px;

width: 200px;

margin: 10px 0;

position: relative;

left: 0;

top: 3em;

transition: left 4s cubic-bezier(0.175, 0.885, 0.32, 1.275), background 2s 500ms;


.transthem {

left: 30%;

background: #00BCD4;


Here, we’ve defined transitions for the left and background properties. The difference is that each item is separated by a comma. The Left transition will last four seconds and use a cubic-bezier timing function. The background transition will only last two seconds, but it begins after a half-second ( 500ms ) delay.

Occasionally, you may need to detect when a transition ends in order to take another action. For example, if you transition opacity: 1 to opacity: 0 , it’s a good idea to add a hidden attribute to the element for improved assistive technology support. This is where the transitionend event comes in handy.

When a transition completes, the browser fires a transitionend event on the affected element-one for each property. We can listen for these events using addEventListener :

const transitionEndHandler = function() {

// Do something.


const element = document.getElementById(‘el’);

element.addEventListener(‘transitionend’, transitionEndHandler);

HTML also supports an ontransitionend attribute. The code above could also be written as follows:

const transitionEndHandler = function() {

// Do something.


const element = document.getElementById(‘el’);

element.ontransitionend = transitionEndHandler;

Let’s put this knowledge to use. In this example, we’ll hide unselected form options when the user picks one. Our (simplified) HTML follows:

<h1>Please select your favorite color of the ones shown below.</h1>




<input type=”radio” name=”favecolor” id=”red”><label for=”red”>Red</label>



<input type=”radio” name=”favecolor” id=”yellow”><label for=”yellow”>Yellow</label> </li>


<input type=”radio” name=”favecolor” id=”blue”><label for=”blue”>Blue</label>



<div id=”thanks” hidden>Thank you for selecting your favorite color.</div>

<button type=”reset”>Reset</button>


And here’s our (also simplified) CSS:

li {

transition: 500ms;


.fade {

opacity: 0;


Add some styles for color and font size, and we end up with the example below.

Now let’s tie it together with JavaScript. First, let’s define an action that adds the fade class—in this case, a change event handler:

const changeHandler = function() {

// Select unchecked radio buttons. Returns a NodeList.

const notfave = document.querySelectorAll( ‘input:not( :checked )’ );

// Create a new array from the NodeList notfave.forEach( function( item ) {

// Find the parent node, and add a ‘fade’ class item.parentNode.classList.add( ‘fade’ );



const form = document.querySelector( ‘form’ );

form.addEventListener( ‘change’, changeHandler );

When the user selects a color, our form element will receive a change event. That in turn triggers the changeHandler method, which adds a fade class to the parent element of each radio button. This is what triggers our transition.

Now let’s take a look at our transitionend handler. It’s slightly different from the other examples in this chapter:

const transitionendHandler = function( domEvent ) {

domEvent.target.setAttribute( ‘hidden’, ” );

document.getElementById( ‘thanks’ ).removeAttribute( ‘hidden’ );


document.addEventListener( ‘transitionend’, transitionendHandler );

Our transitionendHandler accepts a single event object argument. Here, we’ve named it domEvent , but you could name it evt , foo —just about anything. This event object is passed automatically, according to behavior defined by the Document Object Model Level 2 specification. In order to reference this event object within our handler, we need to define it as a parameter for our function.

Every event object includes a target property. This is a reference to the element that received the event. In this case, it’s a list item, and we’re adding a hidden attribute to each ( eventobject.target.setAttribute(‘hidden’, ‘ ‘) ). The last line of our event handler removes the hidden attribute from our “Thank you” message, as seen below.

8. Multiple Transitions and transitionend Events

Transitions of multiple properties trigger multiple transitionend events. A declaration such as transition: Left 4s Linear, background 2s 500ms ease; triggers a transitionend event for the left property and another for background . To determine which transition triggered the event, you can check the propertyName property of the event object:

const transitionendHandler = function ( eventObject ) {
if ( eventObject.propertyName === ‘opacity’ ) {

// Do something based on this value.



Occasionally, a transition will fail to complete. This can typically happen when the property is overridden while it’s in progress-such as when a user action removes the class name. In those situations, the transitionend event won’t fire.

Because of this risk, avoid using the transitionend event to trigger anything “mission critical”, such as a form submission.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *