ESC

Type to search the knowledge base.

CSS Transitions

Animate property changes with transition — timing, delay, which properties to animate, and how transitions differ from keyframe animations.

beginner3 min read
  • css
  • transitions

A transition interpolates a property from its old computed value to a new one when that value changes — hover, class toggle, media query, JS state. You don’t author a timeline of keyframes; you author what can change and how long the blend takes.

Docs: MDN CSS transitions, transition.

Minimal example

.button {
  background: var(--brand);
  transform: translateY(0);
  transition:
    background 150ms ease,
    transform 150ms ease;
}

.button:hover {
  background: var(--brand-hover);
  transform: translateY(-1px);
}

.button:active {
  transform: translateY(0);
}

Shorthand: transition: <property> <duration> <easing> <delay>.

/* All animatable properties — convenient, sometimes wasteful */
.card {
  transition: all 200ms ease;
}

Prefer listing properties so unexpected changes (e.g. width from content) don’t animate.

Discrete vs interpolable

Not everything transitions smoothly:

  • Numbers/colors/lengths/transforms — usually yes
  • display: none ↔ block — not a smooth transition by itself (see @starting-style / WAAPI / class patterns)
  • height: auto — historically painful; modern interpolate-size / grid tricks help in newer engines
.panel {
  transition: opacity 160ms ease, transform 160ms ease;
  opacity: 0;
  transform: translateY(0.25rem);
  pointer-events: none;
}

.panel.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

Toggle visibility with opacity/transform while keeping the element in the tree for the duration, then set display/inert if needed after transitionend.

Timing functions

transition-timing-function: ease;
transition-timing-function: linear;
transition-timing-function: ease-in-out;
transition-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
transition-timing-function: steps(4, end);

UI chrome often uses a slightly custom cubic-bezier for “designed” motion. Spinners and progress segments may want linear or steps.

Delay and staggering

.menu__item {
  opacity: 0;
  transform: translateY(0.25rem);
  transition:
    opacity 160ms ease,
    transform 160ms ease;
}

.menu.is-open .menu__item {
  opacity: 1;
  transform: none;
}

.menu.is-open .menu__item:nth-child(1) { transition-delay: 20ms; }
.menu.is-open .menu__item:nth-child(2) { transition-delay: 40ms; }
.menu.is-open .menu__item:nth-child(3) { transition-delay: 60ms; }

Stagger on open; consider zero delay on close so the menu disappears snappily.

Reduced motion

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

Or surgically disable non-essential transitions. Don’t remove all feedback if color/opacity changes are part of state clarity — but cut spatial motion.

Transitions vs animations

Transition Animation
Trigger Property change Runs when applied
Timeline Implicit A→B @keyframes
Loop No (retrigger) infinite easy
End state New computed style Needs fill-mode sometimes

Interview out-loud

“Transitions interpolate when a computed style changes. I set property, duration, easing, optional delay. I animate transform and opacity for performance, avoid transition: all in large trees, and respect reduced motion. Keyframe animations are for continuous or multi-step timelines; transitions are for state changes.”

Footguns

  1. Transitioning layout props on scroll-linked UI.
  2. Hover-only motion that never appears for keyboard users — pair with :focus-visible.
  3. Snapping because duration is on the end state only — put transition on the base rule.
  4. Listening to transitionend without filtering propertyName when multiple props run.
  5. Infinite accidental transitions when React re-renders thrash classes.

Further reading

Related guides