Transitions

Animate state changes smoothly with transition, choose timing functions, and stick to properties that animate cheaply.

  • Beginner
  • 6 min read
  • Updated 17 Oct 2026

What you will learn

  • Add transitions to hover and focus
  • Choose durations and timing functions
  • Choose performant properties
  • Reduce motion for users who ask

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

CSS
.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:

PropertyExampleMeaning
transition-propertybackground-colorWhat to animate
transition-duration150ms or 0.15sHow long it takes
transition-timing-functionease-outHow speed changes over time
transition-delay50msHow long to wait before starting
CSS
.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

CSS
/* 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:

ValueFeels like
ease (default)Quick start, gentle end
linearConstant speed; mechanical
ease-inSlow start, fast end; good for things leaving
ease-outFast start, slow end; good for things arriving
ease-in-outSlow 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.

CSS
.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:

CSS
.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:

CostPropertiesWhat the browser redoes each frame
Cheapesttransform, opacityUsually only compositing
Moderatecolor, background-color, box-shadowPaint
Expensivewidth, height, margin, padding, top, left, font-sizeLayout, then paint

Prefer transform and opacity for anything that moves or fades:

CSS
/* 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

CSS
.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:

CSS
@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-property fails silently.
  • Animates one way only: the transition is on the :hover rule 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 transition on the base state so it runs in both directions.
  • List specific properties instead of all, and keep durations short, roughly 100 to 400ms.
  • ease-out suits most interface feedback; DevTools has a curve editor for custom curves.
  • Animate transform and opacity for movement and fades; avoid animating layout properties.
  • Respect prefers-reduced-motion by removing movement-based transitions.

Further reading