Home / Articles / Animation & Motion /

animation

CSS transitions from scratch

Smooth state changes with zero JavaScript. Understand every sub-property, pick the right timing, and keep transitions performant.

The transition shorthand

A transition needs at least a property and a duration. The shorthand packs everything into one declaration.

.btn {
  background: oklch(55% 0.22 270);
  transition: background 0.2s ease, transform 0.2s ease;
}

.btn:hover {
  background: oklch(48% 0.25 270);
  transform: translateY(-2px);
}

Timing functions

The timing function controls the acceleration curve. ease is the default, but ease-out feels more natural for entrances and ease-in for exits.

/* named keywords */
transition-timing-function: ease-in-out;

/* cubic-bezier for full control */
transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);

/* step function for sprite-like effects */
transition-timing-function: steps(4, jump-end);
Use cubic-bezier(0.22, 1, 0.36, 1) for a snappy deceleration that feels responsive and polished.

Delay and sequencing

Add a delay to stagger multiple transitions. The fourth value in the shorthand is the delay.

.card {
  opacity: 0.8;
  box-shadow: 0 1px 2px oklch(0% 0 0 / 0.1);
  transition:
    opacity 0.15s ease 0s,
    box-shadow 0.25s ease 0.05s;
}

.card:hover {
  opacity: 1;
  box-shadow: 0 8px 24px oklch(0% 0 0 / 0.15);
}

Which properties to transition

Not every CSS property can be interpolated. Stick to properties the browser can animate cheaply on the compositor thread.

  • Cheap: transform, opacity, filter
  • Moderate: background-color, color, box-shadow
  • Expensive: width, height, top, left — triggers layout
Avoid transition: all. It transitions every changed property, including ones that trigger expensive reflows.

Transitioning to and from display: none

Traditionally impossible, but modern CSS offers @starting-style and transition-behavior: allow-discrete to handle entry/exit transitions.

.tooltip {
  opacity: 0;
  display: none;
  transition: opacity 0.2s ease, display 0.2s allow-discrete;
}

.tooltip.is-visible {
  display: block;
  opacity: 1;

  @starting-style {
    opacity: 0;
  }
}

Performance checklist

Follow these rules to keep transitions at 60 fps.

  • Transition only transform and opacity when possible.
  • Use will-change sparingly — only on elements about to animate.
  • Keep durations under 400 ms for UI feedback; users perceive longer transitions as sluggish.
  • Respect prefers-reduced-motion — disable or shorten transitions.