Transitions, transforms and animation

Which properties can transition, timing functions, transform functions and 3D, keyframe animation and reduced motion.

Transitions need a state change

A transition interpolates between two values of the same property. It fires when a computed value changes, which is why it is almost always paired with a hover, focus or class toggle.

.btn {
  background-color: var(--brand);
  transition: background-color 150ms ease-out, transform 150ms ease-out;
}

.btn:hover { background-color: oklch(50% 0.17 258); transform: translateY(-1px); }

.panel { transition: opacity 200ms ease-out, transform 200ms ease-out; }
.panel[hidden] { opacity: 0; transform: translateY(-4px); }
Timing functionFeel
linearMechanical β€” good for progress bars and spinners
ease-outFast start, gentle finish β€” the default for entering elements
ease-inSlow start β€” for elements leaving the screen
ease-in-outSymmetric β€” for moves within the viewport
cubic-bezier(.2,.8,.2,1)Snappy and modern β€” a common UI default
steps(4, end)Discrete jumps β€” for sprite frames and counters
  • Only animatable properties transition: transform and opacity are cheap, while width, height, top and margin trigger layout on every frame.
  • Property shorthand such as background transitions inconsistently; name the exact properties instead.
  • transition: all also animates things you did not intend β€” including properties a later library changes.
  • A display: none toggle cannot be transitioned; animate visibility and opacity, or use @starting-style.

Transforms and 3D

A transform moves, rotates or scales a box without disturbing the layout around it, and it can be composited on the GPU β€” which is why it is the workhorse of interface motion.

.card:hover   { transform: translateY(-4px) scale(1.02); }
.spinner      { animation: spin 1s linear infinite; }
.flip         { transform: perspective(800px) rotateY(180deg); transform-style: preserve-3d; }
.avatar       { transform: rotate(-3deg); transform-origin: center bottom; }

@keyframes spin { to { transform: rotate(360deg); } }
  • Functions apply right to left, so order changes the result: translate then rotate is not the same as rotate then translate.
  • An element with a transform becomes the containing block for its fixed descendants and creates a stacking context.
  • transform-origin sets the pivot point; the default is the centre of the box.
  • 3D needs perspective on the parent or in the same transform list, plus transform-style: preserve-3d for nested faces.
  • Reserve will-change: transform for elements that animate continuously β€” applied everywhere it just consumes memory.

Keyframes and reduced motion

An animation runs on its own timeline from a set of keyframes, which makes it the right tool for anything that loops, plays on load, or has more than two states.

@keyframes slide-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.toast {
  animation: slide-in 200ms ease-out both;
  animation-delay: 80ms;
}

@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}
  • The animation shorthand takes name, duration, timing, delay, iteration count and fill mode β€” in that loose order, with the first two time values mapped to duration then delay.
  • both as a fill mode keeps the final keyframe applied after the animation ends, instead of snapping back.
  • animation-fill-mode: backwards applies the first keyframe during the delay, which prevents a flash of unstyled position.
  • Beware of looping animations that never pause: they cost battery and can prevent a laptop from suspending.
⚠️
Motion can cause nausea and vertigo for some people. Honour prefers-reduced-motion for anything that moves more than a few pixels, and prefer a short opacity fade over a large translation when motion is reduced.

FAQ

Why does my transition not run?
Either the property is not animatable, the value change happens on an element that was just created, or the change comes from a display toggle. Animate opacity and transform on an element that already exists.
Transition or animation?
A transition needs a trigger and interpolates between two states. An animation runs on its own, can loop, and can define several intermediate states. Reach for the simpler one that fits.

Positioning, stacking and overflow Debugging CSS, resets and browser support

Last refreshed 2026-09-18.