CSS transitions let you smoothly change property values over a set duration, no JavaScript required. Instead of a style jumping instantly from one value to another, a transition gives it a smooth, controlled animation. In this guide, you will learn how transitions work, which properties you can animate, and how to use timing functions to control the feel of your animations.
What is a CSS transition?
A CSS transition is a way to animate the change between two states of an element. When a property value changes (for example, on hover), instead of switching immediately, the browser interpolates between the old and new value over a set time.
Transitions are defined on the element that will change, not on the state that triggers the change. You tell the browser: when any property on this element changes, animate it over X seconds.
/* Basic syntax */ .element { transition: property duration timing-function delay; } /* Example */ .button { background-color: #3b82f6; transition: background-color 0.3s ease; } .button:hover { background-color: #1d4ed8; }
In the example above, when the button is hovered, the background color smoothly changes from blue to a darker blue over 0.3 seconds instead of switching instantly.
The four CSS transition properties
The transition shorthand combines four individual properties. Understanding each one gives you precise control over your animations.
1. transition-property
Specifies which CSS property to animate. You can target a single property, a comma-separated list, or use all to animate every changing property.
/* Animate only opacity */ transition-property: opacity; /* Animate opacity and transform */ transition-property: opacity, transform; /* Animate everything: use carefully */ transition-property: all;
Avoid using all on performance-critical elements: it forces the browser to watch every property and can cause dropped frames on lower-end devices.
2. transition-duration
Sets how long the transition takes, in seconds (s) or milliseconds (ms). There is no default: you must always define a duration, otherwise the transition will not run.
transition-duration: 0.3s; /* 300 milliseconds */ transition-duration: 500ms; /* same as 0.5s */
3. transition-timing-function
Controls the speed curve of the animation: how fast or slow it moves at different points during the transition. This is what makes an animation feel mechanical or natural.
transition-timing-function: ease; /* slow start, fast middle, slow end */ transition-timing-function: linear; /* constant speed */ transition-timing-function: ease-in; /* slow start, fast end */ transition-timing-function: ease-out; /* fast start, slow end */ transition-timing-function: ease-in-out; /* slow start and end */ transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); /* custom curve */
For most UI interactions, ease or ease-out feel the most natural. linear tends to feel robotic unless you are animating something like a loading bar.
4. transition-delay
Sets how long the browser waits before starting the transition. Useful when you want to stagger multiple animations or delay a tooltip from appearing instantly.
transition-delay: 0s; /* no delay (default) */ transition-delay: 0.2s; /* wait 200ms before starting */
The shorthand syntax
You will most often use the shorthand transition property to keep things clean. The order is: property, duration, timing-function, delay.
/* Single property */ .card { transition: transform 0.3s ease-out; } /* Multiple properties: each with its own settings */ .card { transition: transform 0.3s ease-out, box-shadow 0.3s ease, opacity 0.2s linear; } /* Animate all with delay */ .card { transition: all 0.4s ease 0.1s; }
When transitioning multiple properties, list each one separately with a comma. This gives you independent control over the duration and timing of each property.
Which CSS properties can be transitioned? (Animatable Properties)
Not every CSS property is animatable. A property can be transitioned only if the browser can calculate intermediate values between its start and end states. The most commonly transitioned properties include:
- Visual:
opacity,color,background-color,border-color,box-shadow - Size and spacing:
width,height,padding,margin,border-width - Transform:
transform(translate, scale, rotate) - Position:
top,left,right,bottom - Typography:
font-size,letter-spacing,line-height
Properties like display, visibility, and z-index are not on this list. They hold whole-number or keyword values with no in-between state, so the browser has traditionally treated them as instant, all-or-nothing switches rather than values it can interpolate. Modern CSS now has a way around this limitation for a handful of these properties, covered in the section below.
Performance tip: stick to opacity and transform
For the smoothest animations, limit transitions to opacity and transform whenever possible. These two properties are handled by the GPU and do not trigger layout recalculations. Animating width, height, margin, or top/left forces the browser to recalculate the layout on every frame, which can cause dropped frames on slower devices.
/* Smooth: GPU-accelerated */ .element { transition: opacity 0.3s ease, transform 0.3s ease; } /* Avoid: triggers layout recalculation */ .element { transition: width 0.3s ease, margin 0.3s ease; }
Transitioning display and visibility with transition-behavior
For years, transitioning display: none to display: block was impossible in CSS. Since display is a keyword property with no in-between value, browsers switched it instantly, which is why developers reached for opacity and visibility workarounds instead. That is changing. The transition-behavior: allow-discrete declaration, now supported in Chrome, Edge, and Firefox, tells the browser to treat discrete properties like display, visibility, and content-visibility as transitionable by flipping them at the very end (or start) of the transition rather than instantly.
/* Modal that fades AND leaves the layout when closed */ .modal { opacity: 1; display: block; transition: opacity 0.3s ease, display 0.3s ease; transition-behavior: allow-discrete; } .modal.is-hidden { opacity: 0; display: none; } /* Needed so the fade-in starts from opacity 0, not the last known value */ @starting-style { .modal { opacity: 0; } }
Two pieces work together here. transition-behavior: allow-discrete lets display take part in the transition instead of jumping instantly, and the @starting-style at-rule tells the browser what value to animate from when the element first becomes visible, since a newly displayed element has no previous frame to transition from. Browser support is solid in current versions of Chrome, Edge, Safari, and Firefox, but older browsers will simply ignore transition-behavior and fall back to the instant switch, so this is safe to add as a progressive enhancement.
This is different from an element being removed from the DOM. allow-discrete still requires the property change to come from a class toggle or attribute change; it does not add exit animations to elements that JavaScript deletes outright.
Fade in and fade out effects with CSS transitions
A fade is simply an opacity transition, and it is one of the most common effects developers reach for. Because opacity is GPU-accelerated, it is also one of the cheapest animations you can run.
/* Fade in */ .fade-in { opacity: 0; transition: opacity 0.4s ease; } .fade-in.is-visible { opacity: 1; } /* Fade out on hover */ .fade-out-on-hover { opacity: 1; transition: opacity 0.3s ease; } .fade-out-on-hover:hover { opacity: 0.4; }
To fade an element in the moment it scrolls into view, pair the CSS transition above with the IntersectionObserver JavaScript API to toggle the is-visible class when the element enters the viewport. The CSS handles the smoothness; JavaScript only decides when to flip the class.
CSS hover transition effects
Hover is the single most common trigger for a transition, so it is worth seeing a few patterns side by side.
Hover button effect
.btn { background-color: #3b82f6; color: #fff; padding: 0.75rem 1.5rem; border: none; border-radius: 6px; cursor: pointer; transition: background-color 0.2s ease, transform 0.2s ease; } .btn:hover { background-color: #1d4ed8; transform: translateY(-2px); }
Fade in on hover
.overlay { opacity: 0; transition: opacity 0.3s ease; } .card:hover .overlay { opacity: 1; }
Smooth navigation link underline
.nav-link { text-decoration: none; border-bottom: 2px solid transparent; transition: border-color 0.2s ease; } .nav-link:hover { border-color: #3b82f6; }
Card lift on hover
.card { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); transition: box-shadow 0.3s ease, transform 0.3s ease; } .card:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); transform: translateY(-4px); }
Triggering CSS transitions with JavaScript
A transition does not care what changed the property, only that it changed. Adding or removing a class with JavaScript fires the transition exactly the same way a :hover state does, which makes this the standard pattern for animating things like a mobile menu, an accordion, or a toast notification.
.panel { transform: translateX(-100%); transition: transform 0.3s ease-out; } .panel.is-open { transform: translateX(0); }
// JavaScript only toggles the class; CSS handles the motion panel.classList.toggle('is-open');
This same class-toggle pattern is what powers CSS transitions inside component-based frameworks. In React, for example, a piece of state controls the class name, and the browser transitions it exactly as it would on a plain HTML page; libraries like react-transition-group exist mainly to handle the trickier case of animating an element out before it unmounts from the DOM.
CSS transitions vs. page transitions
If your goal is an animated effect between two separate pages, such as a slide or fade when navigating from a listing page to a detail page, the transition property is not the right tool. It only animates property changes on elements that stay in the DOM. Page-to-page navigation is handled by the newer View Transitions API, which can animate the swap between an old page and a new one, including across full page loads in supporting browsers.
/* Opt an entire page into view transitions */ @view-transition { navigation: auto; }
Support for cross-page view transitions is currently limited to Chromium-based browsers, with other engines catching up, so treat it as progressive enhancement rather than a cross-browser solution today. Within a single page, such as animating a to-do item before it is removed from a list, the same API works more broadly and can often be paired with the regular transition property covered throughout this guide.
CSS transitions with Tailwind CSS
Tailwind exposes the same four transition properties as utility classes instead of raw CSS, which is useful if your project is already using the framework.
<!-- Tailwind transition utilities --> <button class="bg-blue-500 hover:bg-blue-700 transition-colors duration-300 ease-in-out"> Hover me </button> <!-- Delay and multiple properties --> <div class="transition-all duration-300 delay-100 hover:scale-105 hover:shadow-lg">...</div>
Tailwind's transition, transition-colors, transition-opacity, and transition-transform classes each scope the transition to a specific group of properties, which mirrors the advice earlier in this guide to avoid transition: all where possible. Pair them with duration-* for timing, ease-* for the timing function, and delay-* for a delay.
CSS transitions vs CSS animations
Both transitions and animations move elements from one state to another, but they serve different purposes:
- Transitions require a trigger (hover, focus, class change) and move between two states: a start and an end.
- Animations (using
@keyframes) can run automatically without a trigger, loop, go through multiple steps, and play in reverse.
Use a transition when reacting to user interaction. Use an animation when you need something to move on its own, repeat, or follow a multi-step path. See the CSS animation guide for a full breakdown of keyframe animations.
Common mistakes to avoid
- Forgetting the duration: A transition with no
transition-durationdoes nothing: the change will still be instant. - Putting transition on the wrong selector: Always put
transitionon the base state, not the:hoverstate. If you put it on:hover, the transition only plays when entering, not when leaving. - Using
allcarelessly:transition: allcatches every property change including ones you did not intend, and affects performance. - Animating layout properties: Transitioning
width,height, orpaddingcauses reflow and is slower than usingtransform: scale(). - Forgetting allow-discrete: Adding
displaytotransition-propertywithouttransition-behavior: allow-discretedoes nothing; the property still switches instantly.
FAQ
Can I transition display: none to display: block?
Not with a plain transition. display normally switches instantly because it has no in-between value. You can still fade an element in or out with opacity, or, in current versions of Chrome, Edge, Safari, and Firefox, use transition-behavior: allow-discrete together with @starting-style to animate display itself, covered earlier in this guide.
What is the difference between transition and animation in CSS?
A transition reacts to a state change such as a hover and moves between two values. An animation uses @keyframes, can loop, run automatically without a trigger, and supports multiple steps, not just start and end.
How do I make a transition work on page load?
Transitions only fire when a property changes. On page load there is no change, so nothing fires. To trigger an animation on load, use CSS @keyframes or toggle a class with JavaScript after the page loads.
Which CSS transition timing function should I use?
ease-out is the best default for most UI interactions: it feels natural because it mimics how real objects decelerate. Use ease-in when something is exiting the screen, and linear only for things like progress bars or spinners.
Can CSS transitions be triggered by JavaScript?
Yes. Any property change triggers a transition, including changes made by JavaScript. Adding or removing a CSS class that changes a property value will cause the transition to fire. This is one of the most common patterns for building interactive UI components.
Does transition work on pseudo-elements like ::before and ::after?
Yes. You can apply transition to ::before and ::after pseudo-elements in the same way you would any other element. This is useful for animated underlines, icon swaps, and decorative effects.
Why is my css transition on display or visibility not working?
Because those properties are discrete: they have no in-between value, so browsers apply them instantly by default no matter what duration you set. Add transition-behavior: allow-discrete alongside display or visibility in your transition-property list, and pair it with @starting-style if the element is appearing rather than disappearing.
Generate CSS animations visually
Our CSS Animation Generator lets you build keyframe animations in real time and copy the exact code into your project.
Open Animation Generator →