Home / Articles / Animation & Motion /

animationtransitions

Transitioning multiple properties

Most interactive elements change more than one property at a time. Here's how to coordinate those transitions without tripping over performance pitfalls.

Comma-separated transition lists

The transition shorthand accepts a comma-separated list. Each entry independently controls a property, duration, timing function, and delay.

.card {
  background: oklch(0.19 0.02 260);
  transform: translateY(0);
  box-shadow: 0 1px 3px oklch(0% 0 0 / 0.12);
  transition:
    background 0.2s ease,
    transform 0.2s ease,
    box-shadow 0.3s ease;
}

.card:hover {
  background: oklch(0.23 0.03 260);
  transform: translateY(-4px);
  box-shadow: 0 12px 32px oklch(0% 0 0 / 0.2);
}

Each property gets its own timing. The box-shadow intentionally runs longer because shadow diffusion looks smoother when it trails the lift.

Why not transition: all?

transition: all 0.2s ease is tempting because it covers every property in one line. But it comes with real costs.

  • It transitions properties you didn't intend — like padding, margin, or height — causing layout thrashing.
  • Adding a new property to the element later may produce unexpected animations.
  • You lose fine-grained control over per-property duration and delay.
/* Avoid — transitions every changed property */
.btn {
  transition: all 0.25s ease;
}

/* Prefer — explicit list of what animates */
.btn {
  transition:
    background 0.2s ease,
    color 0.2s ease,
    transform 0.15s ease;
}
Use transition: all only in rapid prototyping. In production, always list properties explicitly.

Staggering with delay

Adding small delays between properties creates a choreographed feel. The fourth value in each transition entry is the delay.

.nav-link {
  color: oklch(0.93 0.01 260);
  border-bottom: 2px solid oklch(0% 0 0 / 0);
  transform: translateY(0);
  transition:
    color 0.15s ease 0s,
    border-bottom-color 0.2s ease 0.05s,
    transform 0.2s ease 0.02s;
}

.nav-link:hover {
  color: oklch(0.72 0.19 265);
  border-bottom-color: oklch(0.72 0.19 265);
  transform: translateY(-1px);
}

The color changes first, then the underline follows, then the slight lift. Each delay is tiny — 20 to 50 ms — but the sequential motion feels intentional.

Longhand properties for clarity

When the shorthand gets unwieldy, split into longhand declarations. This is especially useful when several properties share the same timing.

.panel {
  transition-property: opacity, transform, background;
  transition-duration: 0.25s, 0.25s, 0.3s;
  transition-timing-function: ease-out;
  transition-delay: 0s, 0.03s, 0s;
}

When a longhand list is shorter than transition-property, values cycle. Here the single ease-out applies to all three properties.

Performance considerations

More properties means more work each frame. Group them by rendering cost:

  • Compositor-only (transform, opacity): free to combine — GPU-accelerated.
  • Paint-only (color, background-color, box-shadow): reasonable in small numbers.
  • Layout-triggering (width, height, padding): avoid transitioning these. Use transform: scale() instead of width.
/* Instead of transitioning width */
.drawer {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}

.drawer.open {
  transform: scaleX(1);
}

Respecting user preferences

Always wrap multi-property transitions in a prefers-reduced-motion check. Users who opt out of motion should still see instant state changes.

@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none;
  }
}

/* Or reduce to only opacity (subtle, non-motion) */
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: opacity 0.15s ease;
  }
}

Removing all transitions is the safest approach. Alternatively, keep only color and opacity changes since those don't involve spatial movement.