Without transitions, a change of state is instant: a button's colour snaps from one value to another on hover. A transition animates between the two values over a short time. Small, quick transitions make an interface feel responsive and help people follow what changed.
A first transition
.button {
background-color: #0b5394;
color: white;
transition: background-color 150ms ease-out;
}
.button:hover,
.button:focus-visible {
background-color: #073763;
}
The transition goes on the element's base state, not on :hover. That way it applies in both directions: into the hover state and back out of it. If you put it only on :hover, the change animates in but snaps back instantly.
The transition properties
The shorthand combines four properties:
| Property | Example | Meaning |
|---|---|---|
transition-property | background-color | What to animate |
transition-duration | 150ms or 0.15s | How long it takes |
transition-timing-function | ease-out | How speed changes over time |
transition-delay | 50ms | How long to wait before starting |
.card {
transition: box-shadow 200ms ease-out, transform 200ms ease-out;
}
Separate several transitions with commas. The first time value in each is the duration; a second time value is the delay.
Avoid transition: all
/* Avoid */
.card {
transition: all 300ms;
}
all animates every property that changes, including ones you did not intend, such as layout properties changed by another class or a media query. It is harder to reason about and can cost performance. List the properties you mean.
Durations
Interface transitions should be short. As a rough guide:
- Small changes, such as colour on hover: around 100 to 200ms.
- Larger movements, such as a panel sliding in: around 200 to 400ms.
Anything much longer starts to feel sluggish, because the user has to wait for the interface to catch up. These are starting points from common practice, not fixed rules; adjust by eye.
Timing functions
The timing function describes how the speed changes across the duration:
| Value | Feels like |
|---|---|
ease (default) | Quick start, gentle end |
linear | Constant speed; mechanical |
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) | A custom curve |
steps(4) | Jumps in four equal steps |
ease-out is a good default for interface feedback: the change starts immediately, so it feels responsive.
.drawer {
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
DevTools has a curve editor: click the small curve icon next to any timing function in the Styles pane to adjust it visually.
What can be transitioned
A property can transition only if the browser can calculate values in between. Colours, lengths, numbers, percentages, shadows and transforms all work. Keywords generally do not:
.menu {
display: none;
transition: display 200ms; /* no smooth effect: display has no in-between */
}
height: auto is another classic case. You cannot transition from 0 to auto in most browsers, because auto is not a number. Common workarounds are animating max-height, grid-template-rows from 0fr to 1fr, or transform.
Newer CSS features are starting to remove these limits. transition-behavior: allow-discrete and @starting-style allow entry and exit transitions for elements that switch display, and interpolate-size allows animating to auto. Support is still uneven, so check Baseline on web.dev and treat them as progressive enhancements.
Performance: animate transform and opacity
Changing some properties forces the browser to recalculate the layout of the page on every frame. Others only need a repaint, and two can usually be handled by the GPU without either:
| Cost | Properties | What the browser redoes each frame |
|---|---|---|
| Cheapest | transform, opacity | Usually only compositing |
| Moderate | color, background-color, box-shadow | Paint |
| Expensive | width, height, margin, padding, top, left, font-size | Layout, then paint |
Prefer transform and opacity for anything that moves or fades:
/* Expensive: changes layout every frame */
.card:hover {
margin-top: -4px;
}
/* Cheap: the layout does not change */
.card {
transition: transform 200ms ease-out, box-shadow 200ms ease-out;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}
Short colour and shadow transitions on hover are fine. The issue is animating layout properties, especially on many elements at once. The rendering lesson near the end of the course explains why.
A complete button
.button {
display: inline-block;
padding: 0.75rem 1.25rem;
border-radius: 0.375rem;
background-color: #0b5394;
color: white;
text-decoration: none;
transition:
background-color 150ms ease-out,
transform 150ms ease-out;
}
.button:hover {
background-color: #073763;
}
.button:active {
transform: scale(0.97);
}
.button:focus-visible {
outline: 3px solid #f0b429;
outline-offset: 2px;
}
The focus outline appears instantly, without a transition. Focus indicators should never fade in slowly, because keyboard users need to see them straight away.
Reduced motion
Some people get dizzy or nauseous from movement on screen, and operating systems offer a "reduce motion" setting. CSS can read it with the prefers-reduced-motion media query:
@media (prefers-reduced-motion: reduce) {
.card,
.button {
transition: none;
}
.card:hover {
transform: none;
}
}
Colour fades are generally fine to keep. Movement, scaling and zooming are what cause problems. The next lesson shows a more systematic approach, and the Web Accessibility Essentials course explains who this helps and why.
Debugging transitions
- Nothing animates: check the transition is on the base state and that the property name is spelt correctly. A typo in
transition-propertyfails silently. - Animates one way only: the transition is on the
:hoverrule instead of the base rule. - Jumps instead of animating: the property, or one of its values such as
auto, cannot be interpolated. - Inspect it slowly: the DevTools Animations panel (in Chrome and Edge, open it from the More tools menu) records transitions and lets you replay them at 25% or 10% speed.
Summary
- Put
transitionon the base state so it runs in both directions. - List specific properties instead of
all, and keep durations short, roughly 100 to 400ms. ease-outsuits most interface feedback; DevTools has a curve editor for custom curves.- Animate
transformandopacityfor movement and fades; avoid animating layout properties. - Respect
prefers-reduced-motionby removing movement-based transitions.