Transitions and Transforms

Introduction

Micro-interactions make interfaces feel responsive and polished, but only when motion is subtle, purposeful, and performant. This chapter introduces CSS transitions and transforms for common UI feedback patterns such as hover lift, button press states, and small reveal effects.

Prerequisites

  • Basic selectors and pseudo-classes (:hover, :focus-visible, :active)
  • Basic understanding of box model and layout
  • Familiarity with accessibility basics for interaction feedback

Transition Basics

A transition animates property changes over time.

css
.button {
  transition: background-color 0.2s ease;
}
 
.button:hover {
  background-color: #1d4ed8;
}

Code explanation:

  • when hover changes background-color, browser interpolates instead of jumping instantly
  • duration is 0.2s, timing curve is ease

Transitions work only when a property value changes from one state to another.

Transition Properties

Longhand properties:

  • transition-property
  • transition-duration
  • transition-timing-function
  • transition-delay

Equivalent shorthand:

css
.card {
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

This is the most common production style: explicitly list animating properties.

Choosing Properties to Transition

Good candidates:

  • opacity
  • transform
  • background-color
  • color
  • box-shadow (moderately)

Use caution with:

  • width, height, top, left, margin (can trigger heavier layout work)

Prefer transform/opacity for smoother UI feedback where possible.

Timing Functions

Common timing functions:

  • linear
  • ease
  • ease-in
  • ease-out
  • ease-in-out
  • cubic-bezier(...)

Example:

css
.chip {
  transition: transform 0.18s ease-out;
}

ease-out often feels natural for hover entry/exit because motion settles smoothly.

Transition Delay

Use delay sparingly:

css
.tooltip {
  transition: opacity 0.15s ease 0.05s;
}

Small delays can reduce accidental flicker. Large delays can make UI feel unresponsive.

Transform Basics

transform visually changes an element without changing normal document flow.

Common transform functions:

  • translate(...)
  • scale(...)
  • rotate(...)
  • skew(...)

Example:

css
.card:hover {
  transform: translateY(-2px);
}

Element appears to lift, while surrounding layout remains stable.

translate()

Moves element along axes:

css
.toast-enter {
  transform: translateY(8px);
}

Useful for subtle in/out motion and positional feedback.

scale()

Resizes visually:

css
.icon-button:hover {
  transform: scale(1.04);
}

Use small scale values to avoid jitter and layout overlap visuals.

rotate()

Rotates element:

css
.chevron-open {
  transform: rotate(180deg);
}

Very useful for accordion arrows and toggle indicators.

Combining Transform Functions

Transforms can be chained in one property:

css
.card:hover {
  transform: translateY(-2px) scale(1.01);
}

Order matters. Function sequence affects final visual result.

Transform Origin

Controls transform anchor point:

css
.menu {
  transform-origin: top right;
}

Useful for dropdowns, popovers, and rotating indicators.

Practical Pattern: Interactive Card

css
.card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  box-shadow: 0 4px 10px rgba(15, 23, 42, 0.08);
}
 
.card:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 24px rgba(15, 23, 42, 0.14);
}

Why this works:

  • lift motion is subtle
  • elevation change reinforces hover state
  • transition remains short and responsive

Practical Pattern: Button Press Feedback

css
.button {
  transition: transform 0.08s ease, background-color 0.2s ease;
}
 
.button:active {
  transform: translateY(1px);
}

Small active press movement improves tactile feel.

Avoid transition: all

css
/* Avoid as default */
/* transition: all 0.3s ease; */

Why avoid:

  • animates unintended properties
  • can introduce performance and debugging issues
  • harder to reason about component behavior

Prefer explicit property lists.

Performance Notes

Generally smoother:

  • transform
  • opacity

Potentially heavier when animated frequently:

  • layout-affecting properties (width, height, margin, left)
  • heavy blur/filter/shadow combinations on large elements

Keep animations short and meaningful.

Tip

Motion Should Communicate State

Animate to clarify interaction (hover, open, close, success), not as decoration on every element.

Accessibility: Reduced Motion

Respect user motion preferences:

css
@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

At minimum, reduce non-essential movement for users who request it.

Common Mistakes

MistakeWhy it hurtsBetter approach
Over-animating many propertiesDistracting and slowAnimate only key properties
Large movement distancesFeels unnaturalKeep motion subtle (1-6px for micro-interactions)
Long durations for simple hoverUI feels laggyKeep hover transitions short (120-250ms)
Using motion without focus/active statesInconsistent interaction feedbackPair hover with keyboard/touch state design
Ignoring reduced-motion preferenceAccessibility issueAdd motion fallback with media query

Debugging Tips

When transition feels wrong:

  1. verify property actually changes between states
  2. inspect computed transition values in DevTools
  3. shorten duration to test responsiveness
  4. ensure transform is not overwritten by another rule
  5. test with reduced-motion setting enabled

Use DevTools force state (:hover, :active, :focus-visible) for consistent testing.

Mini Exercise

  1. Create a card with hover lift (translateY) and shadow transition
  2. Add a button with active press feedback
  3. Add a rotating chevron icon for open/closed state
  4. Replace any transition: all with explicit property lists
  5. Add reduced-motion fallback and test behavior

FAQ

Should I use transitions or keyframe animations for hover effects?

For simple state-to-state changes, transitions are usually simpler and more maintainable.

Why does my transition not run?

Most common reasons: property did not change, selector state never activated, or transition property list does not include the changing property.

Is animating box-shadow always bad?

Not always, but heavy shadow animation on many large elements can be expensive. Use subtly and test performance.

Why use transform for movement instead of top/left?

Transform-based movement often avoids layout recalculation and feels smoother for micro-interactions.

What is a good default transition duration?

For UI micro-interactions, around 150-250ms is a practical starting range.