Episode 1 — Fundamentals / 1.12 — CSS Animations and Motion Design

1.12.a — CSS Transitions

In one sentence: A transition smoothly interpolates a CSS property from one value to another over a specified duration — the simplest way to add motion to your UI.

Navigation: ← 1.12 Overview · 1.12.b — CSS Transform →


1. The transition shorthand

/* transition: property  duration  timing-function  delay; */
   transition: opacity   300ms     ease-in-out       0s;
ComponentWhat it controlsDefault
propertyWhich CSS property to animate (all = every animatable prop)all
durationHow long the change takes0s (instant)
timing-functionAcceleration curveease
delayWait before the transition starts0s

Longhand equivalents

transition-property: opacity;
transition-duration: 300ms;
transition-timing-function: ease-in-out;
transition-delay: 0s;

2. Which properties can be transitioned?

A property is animatable if the browser can compute intermediate values.

Animatable (common)NOT animatable
opacity, color, background-colordisplay (no intermediate between none / block)
width, height, margin, paddingfont-family (can't blend fonts)
transform, box-shadow, border-radiusposition (static / relative — keyword, not numeric)
top, left, right, bottomvisibility* (special — can only snap at 0 / end)

Rule of thumb: if a value is numeric or color-based, it's probably animatable.


3. Timing functions explained

  ease           ──╮     ╭── fast start, slow end (default)
                    ╰───╯
  linear         ────────── constant speed

  ease-in        ──╮        slow start, fast end
                    ╰──────
  ease-out       ──────╮    fast start, slow end
                        ╰──
  ease-in-out    ──╮  ╭──   slow-fast-slow (symmetric S-curve)
                    ╰╯

cubic-bezier() — custom curves

Every keyword maps to a cubic-bezier:

ease          → cubic-bezier(0.25, 0.1, 0.25, 1.0)
linear        → cubic-bezier(0.0,  0.0, 1.0,  1.0)
ease-in       → cubic-bezier(0.42, 0.0, 1.0,  1.0)
ease-out      → cubic-bezier(0.0,  0.0, 0.58, 1.0)
ease-in-out   → cubic-bezier(0.42, 0.0, 0.58, 1.0)

You can create overshoot / bounce effects by pushing control points beyond 0–1:

transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);

4. Duration psychology

DurationUser perceptionUse case
100–200 msFeels instantHover color changes, small icon tweaks
200–300 msFeels responsiveButton state changes, tooltips
300–500 msFeels smoothPanel slides, modal entrances
> 500 msFeels slow or cinematicUse sparingly — page-level transitions, dramatic reveals

Interview tip: "I default to 200–300 ms for micro-interactions and reserve anything longer for deliberate choreography."


5. Multiple transitions

Comma-separate each property:

.card {
  transition: transform 300ms ease-out,
              box-shadow 300ms ease-out,
              opacity 200ms linear;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  opacity: 0.95;
}

Using transition: all 300ms is convenient but can cause unintended animations on properties you didn't plan for (e.g., background-color changing on theme switch). Prefer listing properties explicitly.


6. Transition events in JavaScript

element.addEventListener('transitionend', (e) => {
  console.log(`${e.propertyName} finished in ${e.elapsedTime}s`);
});
EventWhen it fires
transitionrunTransition is created (including during delay)
transitionstartDelay has elapsed; interpolation begins
transitionendInterpolation finishes
transitioncancelTransition is cancelled mid-way

7. Common patterns

Hover lift

.btn {
  transition: transform 200ms ease-out, box-shadow 200ms ease-out;
}
.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

Color change

.link {
  color: #333;
  transition: color 150ms ease;
}
.link:hover {
  color: #0066ff;
}

Size change (with caution)

.avatar {
  width: 48px;
  height: 48px;
  transition: width 300ms ease, height 300ms ease;
}
.avatar:hover {
  width: 64px;
  height: 64px;
}

Performance note: animating width/height triggers layout every frame. Prefer transform: scale() for the same visual effect at a fraction of the cost (see 1.12.e).


8. Accessibility: prefers-reduced-motion

Some users experience motion sickness, vestibular disorders, or simply prefer less movement. Respect their OS setting:

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

Why 0.01ms instead of 0s? A zero duration can break transitionend event listeners. A near-zero value still fires the event but removes visible motion.


9. Key takeaways

  1. transition smoothly animates one property change from A → B.
  2. List properties explicitly rather than relying on all.
  3. Keep micro-interactions in the 200–300 ms range.
  4. Use cubic-bezier() for custom curves or playful overshoot.
  5. Always include a prefers-reduced-motion fallback.

Explain-It Challenge

Explain without notes:

  1. Why transition: all 500ms can cause unexpected behavior when a theme class is toggled.
  2. How transitionend fires once per animated property — and what that means if you transition three properties simultaneously.
  3. Why 0.01ms is preferable to 0s in a prefers-reduced-motion reset.

Navigation: ← 1.12 Overview · 1.12.b — CSS Transform →