Himanshu SharmaSoftware engineer
Let’s talk

CSS · Lesson 25 of 29

Transitions

Animate changes smoothly between two states with CSS transitions, including duration, easing, delay, which properties to animate, and how to keep them fast.

12 minPolishUpdated 18 October 2026

By the end of this lessonAdd smooth hover, focus and state transitions, choose durations and easing that feel right, and animate only properties that stay fast.

From one state to another

Normally, when a property changes, say a button's colour on hover, the new value appears instantly. A transition tells the browser to animate the change over a short time. It is the simplest kind of CSS animation: you describe a start and an end state, and the browser fills in the frames.

html
<style>
  .btn {
    padding: 10px 22px;
    border: 0;
    border-radius: 8px;
    background: #4f46e5;
    color: white;
    font-size: 16px;
    cursor: pointer;
    transition: background-color 0.25s ease, transform 0.25s ease;
  }
  .btn:hover { background: #7c3aed; transform: translateY(-3px); }
  .btn:active { transform: translateY(0); }
</style>
<button class="btn">Hover over me</button>

Move the pointer on and off. The colour and position change smoothly in both directions, because the transition is declared on .btn (the base state), not only on :hover.

The transition shorthand

css
transition: property duration timing-function delay;
  • property: what to animate: background-color, transform, opacity, or all. Name properties explicitly; all can animate things you did not intend.
  • duration: how long, in seconds (0.25s) or milliseconds (250ms). Required.
  • timing-function: the pacing curve.
  • delay: wait before starting.

You can list several transitions separated by commas, as above.

How long?

The right duration depends on the size of the change:

  • 100–200 ms for small feedback: hover colour, button press.
  • 200–400 ms for larger movement: menus, cards, panels.
  • Over 500 ms feels slow for interface responses.

Quick and quiet is better than showy. The aim is to make a change understandable, not to be noticed.

Easing: the feel of motion

The timing function controls speed along the way:

  • ease (default): quick start, gentle end.
  • ease-out: starts fast, slows down. Good for things entering, as they arrive naturally.
  • ease-in: starts slow, speeds up. Good for things leaving.
  • ease-in-out: slow at both ends.
  • linear: constant speed. Right for spinners and progress, wrong for most UI movement.
  • cubic-bezier(x1, y1, x2, y2): a custom curve; steps(n) jumps in stages.
css
.menu { transition: opacity 0.2s ease-out, transform 0.2s ease-out; }

Transition the base state

A very common mistake is putting the transition only in the hover rule:

css
/* Animates on the way in, snaps back when you move away */
.card:hover { transition: transform 0.3s; transform: scale(1.03); }

/* Animates both ways */
.card { transition: transform 0.3s; }
.card:hover { transform: scale(1.03); }

Put it on the element itself and every change to the listed properties animates, whichever way it goes.

What can and cannot be animated

A property can be transitioned only if the browser can calculate in-between values: colours, lengths, numbers, transforms. Things with no middle ground, such as display: none to block, position, or font-family, switch instantly.

To fade something in and out, animate opacity (and combine with visibility, or the hidden attribute when finished). Modern CSS has transition-behavior: allow-discrete and @starting-style to animate display, but support is still arriving, so check before you rely on them.

Keep it fast

Animating some properties forces the browser to re-calculate layout or repaint on every frame:

  • Cheap: transform (move, scale, rotate) and opacity.
  • Costly: width, height, top, left, margin, padding, box-shadow on big areas.

Where you can, express movement with transform: translate() rather than left/top, and size changes with transform: scale(). Your page will stay smooth on cheap phones.

Accessible motion

Not everyone enjoys movement. For some, large or constant motion causes dizziness. Provide a calm version for those who ask:

css
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; }
}

Also:

  • Do not rely on a transition to communicate something essential; the end state must be clear on its own.
  • Keep :focus-visible styles immediate and obvious.

Example: a card that lifts

html
<style>
  .card {
    max-width: 220px; padding: 20px; border-radius: 12px; background: white;
    box-shadow: 0 2px 6px rgb(0 0 0 / .12);
    transition: transform .2s ease-out, box-shadow .2s ease-out;
  }
  .card:hover, .card:focus-within { transform: translateY(-4px); box-shadow: 0 10px 24px rgb(0 0 0 / .18); }
  body { background: #f1f5f9; padding: 20px; }
  @media (prefers-reduced-motion: reduce) { .card { transition: none; } }
</style>
<div class="card"><h3 style="margin-top:0">Lift</h3><a href="#">Read more</a></div>

:focus-within makes the card lift for keyboard users too.

Common mistakes

  • Transition only on :hover, so it snaps back.
  • transition: all animating unexpected properties.
  • Very slow transitions that make the interface feel sluggish.
  • Animating layout properties on large elements.
  • Hover-only effects with no keyboard or touch equivalent.

Exercise

Create a link-style button whose underline grows from left to right on hover (use a pseudo-element or background-size), with a 0.25s transition, and no motion for people who prefer reduced motion.

Show one possible answer
html
<style>
  .link {
    text-decoration: none; color: #1d4ed8; font-weight: 600;
    background: linear-gradient(currentColor, currentColor) no-repeat left bottom / 0 2px;
    transition: background-size .25s ease-out;
  }
  .link:hover, .link:focus-visible { background-size: 100% 2px; }
  @media (prefers-reduced-motion: reduce) { .link { transition: none; } }
</style>
<a class="link" href="#">Read the guide</a>

Key points

  • A transition animates a property from its old value to its new value instead of switching instantly.
  • Put the transition on the base state so it works in both directions, not only on :hover.
  • transition takes property, duration, timing function and delay; durations of 150 to 300 ms suit most interface changes.
  • Animate transform and opacity where possible, since they are cheap for the browser.
  • Respect prefers-reduced-motion and never make important feedback depend on motion alone.

Check yourself

1. Where should the transition declaration usually go?
2. Which properties are cheapest to animate?
3. What does transition: background 0.3s ease 0.1s mean?
4. Which of these cannot be transitioned smoothly?

Free tools for this lesson