Introduction#
CSS transitions allow you to animate property changes smoothly over a duration — turning sudden visual changes into fluid, natural motion. Unlike keyframe animations, transitions are state-driven: the browser animates a property from one value to another automatically when that value changes. They are perfect for UI enhancements like hover effects, color fades, button presses, and expanding menus.
What Are CSS Transitions?#
A transition animates the change between two states of a property.
When a property’s value changes (like background-color, width, or opacity), the browser interpolates between old and new values
over the time.
1
2
3
4
5
6
7
8
.element {
background-color: #007bff;
transition: background-color 0.3s ease;
}
.element:hover {
background-color: #0056b3;
}
Here, when you hover, the background color changes smoothly over 0.3s instead of instantly.
How Transitions Work#
A transition only occurs when a property changes — usually due to:
Pseudo-classes (
:hover,:focus,:active)Class toggling via JavaScript
Inline style changes (e.g.,
element.style.height = "200px")DOM manipulation or layout updates
1
2
3
4
const box = document.querySelector(".box");
box.addEventListener("click", () => {
box.classList.toggle("slide-in");
});
1
2
3
4
5
6
7
8
9
.box {
width: 100px;
height: 100px;
transition: transform 0.3s ease-in-out;
}
.box.slide-in {
transform: translateX(-4px);
}
Transition Properties#
CSS provides a set of transition-* properties to control transitions:
1. transition-property#
The transition-property CSS property specifies which CSS properties should transition.
1
2
3
.element {
transition-property: opacity;
}
You can list multiple properties separated by commas.
Use
allto animate every animatable property (not recommended for performance reasons).nonedisables the transitions.
Tip: Animating
allcan be expensive for performance. For high-performance transitions, focus on properties that do not affect the layout (known as non-reflow properties), primarilyopacityandtransform(e.g.,translate,scale).
2. transition-duration#
The transition-duration CSS property defines how long the transition takes to complete. The duration can be specified either in seconds (s) or milliseconds (ms).
1
2
3
4
5
6
.element {
transition-property: opacity;
/* Duration */
transition-duration: 0.3s;
}
You can specify multiple durations for multiple properties, maintaining the order defined in transition-property property:
1
2
3
4
5
6
.element {
transition-property: opacity, transform;
/* Matches the order: opacity gets 0.3s, transform gets 0.6s */
transition-duration: 0.3s, 0.6s;
}
3. transition-timing-function#
The transition-timing-function CSS property controls the speed curve of the transition — essentially, how the intermediate states are calculated over time.
1
2
3
4
5
6
7
.element {
transition-property: opacity;
transition-duration: 0.3s;
/* Timing function */
transition-timing-function: ease-in-out;
}
Common Timing Functions:
| Function | Description |
|---|---|
linear |
Constant speed throughout. |
ease |
Starts slow, speeds up, then slows down (default). |
ease-in |
Starts slow, speeds up at end. |
ease-out |
Starts fast, slows down at end. |
ease-in-out |
Slow start and end, fast middle. |
steps(n, start/end) |
Jump-based transitions for discrete steps. |
cubic-bezier(x1, y1, x2, y2) |
Custom curve for precise control. |
Analogy: Think of the transition as a car accelerating from a stoplight.
linear: The car instantly hits a constant speed and holds it. (Unnatural)
ease-in: The car starts slowly and then slams the gas at the end. (Hard start)
ease-out: The car accelerates quickly and then slowly coasts to its final stop. (Soft landing)
ease-in-out: The car accelerates smoothly and slows down smoothly, providing the most natural, polished feel.
You can specify multiple timing functions for multiple properties, maintaining the order defined in transition-property property:
1
2
3
4
5
6
7
.element {
transition-property: opacity, transform;
transition-duration: 0.3s, 0.6s;
/* Matches the order: opacity gets ease-out, transform gets ease-in-out */
transition-timing-function: ease-out, ease-in-out;
}
Note on
cubic-bezier: This function gives you granular control over the curve, allowing you to simulate complex effects like a subtle spring or bounce (where the property briefly overshoots its final value) in your transitions.
4. transition-delay#
The transition-delay CSS property defines how long to wait before the transition starts.
1
2
3
4
5
6
7
8
.element {
transition-property: opacity;
transition-duration: 0.3s;
transition-timing-function: ease-in-out;
/* Delay */
transition-delay: 0.2s;
}
This is useful for staggering multiple transitions.
You can specify multiple delays for multiple properties, maintaining the order defined in transition-property property:
1
2
3
4
5
6
7
8
.element {
transition-property: opacity, transform;
transition-duration: 0.3s, 0.6s;
transition-timing-function: ease-out, ease-in-out;
/* Matches the order: opacity gets 0s delay, transform gets 0.1s delay */
transition-delay: 0s, 0.1s;
}
Negative Delays#
A negative value for transition-delay is perfectly valid. It tells the browser to start the transition immediately, but from a state corresponding to a point partway through its defined duration.
How It Works:
The negative value essentially “rewinds” the transition’s internal clock by that amount.
For example, applying transition-delay: -0.5s to a transition with a 1s duration means the transition starts instantly,
but it visually skips the first half (0.5s) of its defined movement. The element appears at the midpoint of the transition’s change,
and the remaining 0.5s of the transition will play out.
Key Use Cases:
-
Pre-Positioning The main power of a negative delay in transitions is pre-positioning elements at an intermediate state. Transitions, unlike animations, only define a start and end state (e.g., normal state and
:hoverstate). -
Pre-Setting the State: This is incredibly useful if you want an element to visually appear as if it is already partway through its transition when an event is triggered (like a hover or a class change).
Scenario Code Result Normal State .element { transition: opacity 1s; transition-delay: 0s; } Transition starts at 0% opacity and fades in over 1 second. Pre-Positioned State .element { transition: opacity 1s; transition-delay: -0.5s; } Transition starts immediately, but visually at 50% opacity (the halfway point), and completes the remaining fade over 0.5 seconds.
This technique eliminates the need to define a separate intermediate CSS rule just to position an element slightly advanced in its transition when the page loads or a state change occurs.
5. transition#
Instead of writing multiple CSS transition properties separately, you can combine them into the transition shorthand property.
1
transition: <property> <duration> <timing-function> <delay>;
Example:
1
2
3
4
.element {
/* Transition the 'opacity' property over 0.3s, using the ease-out curve, starting after a 0.1s delay. */
transition: opacity 0.3s ease-out 0.1s;
}
You can also specify multiple transitions:
1
2
3
.element {
transition: opacity 0.3s ease-out 0.1s, transform 0.6s ease-in-out 0s;
}
The Critical Order Rule: Duration vs. Delay
The order of the four components is flexible, except for the two time values (duration and delay):
The first time value the browser reads is always the
transition-duration.The second time value is always the
transition-delay.
This means if you provide only one time value, the browser assumes it is the duration and the delay defaults to 0s.
| Shorthand | Interpretation |
|---|---|
transition: opacity 1s ease; |
duration: 1s, delay: 0s |
transition: opacity 0.5s linear 1s; |
duration: 0.5s, delay: 1s |
Missing values (<property>, <timing-function>) will default to their initial states (all and ease, respectively).
Transition Events#
You can listen to events triggered during or after transitions:
| Event | Description |
|---|---|
transitionrun |
Fired right before transition starts. |
transitionstart |
Fired when a transition begins. |
transitionend |
Fired after transition completes. |
transitioncancel |
Fired if a transition is interrupted. |
Example:
1
2
3
box.addEventListener("transitionend", () => {
console.log("Transition completed!");
});
Debugging Transitions#
If transitions feel janky:
Check if layout properties (
width,height) are being animated.Use DevTools → Performance → “Paint” view to detect repaints.
Replace layout-affecting transitions with
transform: scale()ortranslate().
The will-change property#
Modern browsers optimize animations using the will-change property. It hints to the browser which properties are likely to change, allowing pre-optimization like promoting elements to their own layer, reducing lag for expensive transitions.
1
2
3
.card {
will-change: transform, opacity;
}
Use sparingly — excessive will-change declarations force the browser to immediately allocate memory and GPU resources (layer creation), which can increase memory usage and reduce performance across the rest of the page. Only apply will-change just before the transition is about to occur and remove it immediately after, if possible, to avoid continuous memory use.
Interactive Example#
Reading about timing functions is one thing, but feeling them is another. Use this interactive playground to experiment
with different easing curves and durations. Notice how a slight change in the cubic-bezier coordinates can turn a
robotic movement into a snappy, organic interaction.
Playground name: Demostrating CSS Transitions #
Best Practices#
Optimal Duration is Key: Aim for short durations, typically between 150–500ms, for user interface (UI) interactions. Transitions faster than 150ms can be missed, and those slower than 500ms can feel sluggish. 🐢
Prioritize Performance Properties (GPU-Accelerated): Avoid animating layout-affecting properties like width, height, top, or left. These trigger costly layout re-calculations (reflows). Instead, animate opacity or transform (e.g., scale, translate, rotate), as they run directly on the GPU (Graphics Processing Unit) and are significantly more performant.
Define Transitions in the Base State: Always declare the transition properties in the base selector (e.g., .element), not in the state selectors (:hover, .active). This ensures the transition applies both when entering the new state and when returning to the original state.
Leverage Shorthand and transform: Use the transition shorthand property for brevity and clarity. Always combine transitions with transform properties to create smooth, efficient motion that avoids taxing the main CPU thread.
Control the Exit: Use transition: none; (or explicitly set transition-duration: 0s;) when you need an instant visual update (e.g., hiding a pop-up or for accessibility toggles). This prevents unwanted animation on specific state changes.
Use will-change Judiciously: The will-change property should be used sparingly and briefly. Overuse forces the browser to prematurely allocate memory and GPU resources for every element, which can lead to increased memory usage and overall performance degradation across the page.
Conclusion#
CSS transitions are a lightweight, intuitive way to animate state changes. By using them correctly, particularly by leveraging GPU-accelerated properties like transform and opacity, you can ensure your UI motion is smooth, responsive, and performant. Understanding all transition-* properties, the nuances of timing functions, and strategically using will-change provides the control you need. Master them, and you’ll build a strong foundation for crafting delightful micro-interactions and tackling more advanced CSS animations.
While transitions are excellent for simple, state-driven changes, complex motion that requires more than two steps, looping, or scroll-linking necessitates CSS Keyframe Animations. For a full feature comparison, see the Keyframes vs. Transitions Comparison Table in my An Interactive Guide to CSS Keyframes post.
Key Takeaways:
Use them for micro-interactions (hover, focus, click)
Combine with transform for best performance
Optimize with will-change only when necessary
Keep motion subtle and consistent
Table of contents
I hope you found this post informative and helpful. It took a lot of work to create, and I’m thrilled to finally share it with the world. Thank you for reading. 💖