A transition in one example
A transition tells the browser: when this property changes, animate it over this much time instead of switching at once. Put it on the element's normal rule, not only on :hover:
Hover the first button: its color fades to green and it rises 4px over 0.4 seconds, and moving the pointer away plays the same transition back. The second button has transition: none and switches instantly. Keyboard users get the same effect through :focus-visible.
The transition shorthand
transition: background-color 0.3s ease-in-out 0.1s;
/* property duration timing delay */
| Longhand | What it sets | Default |
|---|---|---|
transition-property | which properties animate (all, or a list) | all |
transition-duration | how long, in s or ms | 0s |
transition-timing-function | the speed curve | ease |
transition-delay | wait before starting (can be negative) | 0s |
The duration default is 0s, which is why nothing animates until you set one. In the shorthand, the first time is always the duration and the second is the delay. For different settings per property, separate them with commas:
.card {
transition: transform 0.2s ease-out, box-shadow 0.4s ease-out;
}
Timing functions side by side
The timing function decides how the speed changes during the transition. Press Play and all seven bars move the same distance in the same 1.5 seconds:
| Timing function | Feels like |
|---|---|
linear | constant speed, mechanical |
ease (default) | quick start, gentle stop |
ease-in | slow start, fast end: good for things leaving |
ease-out | fast start, slow end: good for things arriving |
ease-in-out | slow at both ends |
cubic-bezier(x1, y1, x2, y2) | your own curve; a y above 1 overshoots and settles back |
steps(n) | jumps in n equal steps, no in-between frames |
For hover effects and menus, ease-out with a duration between 150ms and 300ms feels responsive.
What can be transitioned
Properties with values in between can be transitioned: colors, lengths (width, padding, margin), opacity, transform, box-shadow, border-radius, filter, background-color. Properties that jump between keywords, such as display or font-family, switch at once.
height: auto is the one people hit most: a panel that opens from height: 0 to height: auto does not animate by default, because the browser does not calculate the values between 0 and "auto". A reliable workaround is a grid wrapper whose single row goes from 0fr to 1fr:
aria-expanded tells screen reader users whether the panel is open, which the animation alone does not.
Running code when a transition ends
The transitionend event fires when a transition finishes, once per property. Use it to do something after the motion, such as moving focus or removing an element:
Respect reduced motion
Some people turn on "reduce motion" in their system settings because movement on screen makes them dizzy or distracts them. Keep color and opacity fades, and drop large movements, with a media query:
@media (prefers-reduced-motion: reduce) {
.btn { transition: background-color 0.2s ease; } /* no movement, only the color */
}
Keep transitions smooth
transform and opacity are the cheapest properties to animate: the browser can move and fade an element without recalculating the layout. Transitioning width, height, top or margin makes the browser lay out the page again on every frame, which can stutter on slow phones. To move something, prefer transform: translate() over left or margin. The transform page covers the functions.
Common mistakes
transitiononly in the:hoverrule. The effect animates in but snaps back when the hover ends. Puttransitionon the base rule.- A duration without a unit.
transition: opacity 300is invalid. Write300msor0.3s. transition: all. It animates every property that changes, including ones you did not mean to animate, and can make layout changes sluggish. List the properties.- Expecting
display: noneto fade.displayswitches at once. Fadeopacity(and setvisibility: hiddenwhen the fade ends) instead. - Transitioning to
height: auto. It jumps. Use thegrid-template-rowstechnique above. - Long durations on small interactions. Anything over about 400ms on a button or menu feels slow.
Frequently Asked Questions
How do CSS transitions work?
You declare on an element which properties should animate and for how long, for example transition: background-color 0.3s ease;. When that property's value changes (through :hover, a class, or JavaScript), the browser animates from the old value to the new one instead of switching at once.
What is the order of values in the transition shorthand?
transition: property duration timing-function delay;, for example transition: opacity 0.3s ease-in 0.1s;. The only strict rule is that the first time value is the duration and the second is the delay. Separate several transitions with commas.
Why is my CSS transition not working?
Common causes: the transition is only in the :hover rule (so it animates in and snaps back), the property cannot be animated (display, or height to auto), the duration has no unit (0.3 instead of 0.3s), or the value changes at the same moment the element is added to the page.
Can you transition height: auto?
Not with a plain height transition in every browser: interpolating to auto needs the newer interpolate-size property, which is not supported everywhere yet. Common workarounds are animating grid-template-rows from 0fr to 1fr on a grid wrapper, or animating max-height to a value larger than the content.
What is the difference between transition and animation in CSS?
A transition runs once, between two values, when a property changes. An animation uses @keyframes, can have many steps, starts on its own when the page loads, and can repeat or loop.