Ad – 728×90
✨ CSS Effects

CSS Transitions – Adding Smooth State Changes

Without transitions, CSS state changes are instant — hover a button and the color snaps immediately. With transitions, changes animate smoothly over time, making interfaces feel polished and alive. CSS transitions are the easiest way to add motion to a web page — no JavaScript, no libraries, just a single CSS property. In this lesson you will learn every transition property, how to choose timing functions, which properties are safe to animate for performance, and the most common real-world patterns.

⏱️ 18 min read 🎯 Beginner 📅 Updated 2026 👁️ Lesson 1 of 4

How Transitions Work

A transition watches for a CSS property to change (usually triggered by :hover, :focus, a class being added/removed via JavaScript) and animates that change over a specified duration instead of applying it instantly.

CSS – Basic transition
/* Apply transition on the ELEMENT, not on the :hover state */
.btn {
  background-color: #1572B6;
  transition: background-color 0.3s ease;
}

/* When this changes, it will animate over 0.3s */
.btn:hover {
  background-color: #0d5fa0;
}
▶ Hover these buttons to see transitions

The Four Transition Properties

transition-property

Which CSS properties to animate. Only changes to listed properties will transition.

CSS
transition-property: all;                    /* all animatable properties */
transition-property: background-color;       /* only bg color */
transition-property: transform, opacity;     /* specific list */
transition-property: none;                   /* disable all transitions */
⚠️
Avoid transition: all in production

transition: all watches every property for changes and tries to animate them. This can animate properties you don't intend (like width during a layout change) and causes performance issues. Always list specific properties.

transition-duration

CSS
transition-duration: 0.3s;    /* 300ms — good for color/opacity */
transition-duration: 200ms;   /* same as 0.2s */
transition-duration: 0.5s;    /* slower — for larger movements */
transition-duration: 0s;      /* instant (override to disable) */

/* Rule of thumb for UI transitions:
   100–200ms: micro-interactions (hover, focus ring)
   200–400ms: element appearing/disappearing, size changes
   400–600ms: page sections sliding in
   600ms+: usually too slow — feels sluggish */

transition-timing-function

Controls the acceleration curve — how fast the transition moves at each point.

CSS – Timing functions
/* Named keywords */
transition-timing-function: ease;         /* slow start, fast middle, slow end (default) */
transition-timing-function: linear;       /* constant speed — use for spinners */
transition-timing-function: ease-in;      /* slow start, fast end — things leaving */
transition-timing-function: ease-out;     /* fast start, slow end — things arriving */
transition-timing-function: ease-in-out; /* slow start and end — balanced */

/* cubic-bezier() — full control over the curve */
/* cubic-bezier(x1, y1, x2, y2) — values 0–1 for x, any for y */
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);   /* Material Design standard */
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring overshoot */

/* steps() — discrete jumps (sprite animation) */
transition-timing-function: steps(4, end);   /* 4 equal frames */
transition-timing-function: step-start;      /* jump immediately */
transition-timing-function: step-end;        /* jump at end */

transition-delay

CSS
transition-delay: 0s;      /* no delay (default) */
transition-delay: 0.1s;    /* wait 100ms before starting */
transition-delay: -0.2s;   /* negative: starts 0.2s INTO the transition */

/* Stagger effect on multiple elements */
.item:nth-child(1) { transition-delay: 0s;    }
.item:nth-child(2) { transition-delay: 0.05s; }
.item:nth-child(3) { transition-delay: 0.1s;  }
.item:nth-child(4) { transition-delay: 0.15s; }
Ad – 336×280

transition Shorthand

CSS – shorthand
/* transition: property duration timing-function delay */
transition: background-color 0.3s ease 0s;

/* Multiple transitions — comma separated */
.card {
  transition:
    background-color 0.3s ease,
    transform        0.25s ease-out,
    box-shadow       0.3s ease,
    opacity          0.2s ease;
}

/* Common real-world patterns */
.btn     { transition: background-color 0.2s ease, transform 0.15s ease; }
.link    { transition: color 0.2s ease; }
.overlay { transition: opacity 0.3s ease; }
.drawer  { transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1); }

Animatable Properties – Performance Matters

Not all CSS properties are equal to animate. The browser's rendering pipeline determines performance:

CSS – Animation performance tiers
/* ✅ BEST: Compositor-only — GPU, no layout or paint */
transition: transform 0.3s ease;    /* translateX, scale, rotate, etc. */
transition: opacity  0.3s ease;     /* fade in/out */

/* ⚠️ OK: Paint-only — triggers repaint, not reflow */
transition: color            0.2s ease;
transition: background-color 0.3s ease;
transition: border-color     0.2s ease;
transition: box-shadow       0.3s ease;
transition: outline          0.15s ease;

/* ❌ AVOID for frequent animation: triggers reflow */
/* These force the browser to recalculate layout every frame */
transition: width    0.3s ease;   /* reflow */
transition: height   0.3s ease;   /* reflow */
transition: margin   0.3s ease;   /* reflow */
transition: padding  0.3s ease;   /* reflow */
transition: top      0.3s ease;   /* reflow — use transform: translateY() instead */
transition: left     0.3s ease;   /* reflow — use transform: translateX() instead */
transition: font-size 0.3s ease;  /* reflow */

/* Rule: use transform instead of position/size for movement */
/* SLOW: */  .modal { left: -100%; } .modal.open { left: 0; }
/* FAST: */  .modal { transform: translateX(-100%); } .modal.open { transform: translateX(0); }

Real-World Patterns

CSS – Common transition patterns
/* 1. Button hover */
.btn {
  background-color: #1572B6;
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(0,0,0,.15);
  transition: background-color 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}
.btn:hover {
  background-color: #0d5fa0;
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(0,0,0,.2);
}
.btn:active { transform: translateY(0); }

/* 2. Fade in/out */
.tooltip { opacity: 0; pointer-events: none; transition: opacity 0.2s ease; }
.tooltip.visible { opacity: 1; pointer-events: auto; }

/* 3. Slide drawer */
.drawer {
  transform: translateX(-100%);
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.drawer.open { transform: translateX(0); }

/* 4. Card lift on hover */
.card {
  box-shadow: 0 2px 8px rgba(0,0,0,.1);
  transform: translateY(0);
  transition: box-shadow 0.3s ease, transform 0.25s ease;
}
.card:hover {
  box-shadow: 0 12px 32px rgba(0,0,0,.18);
  transform: translateY(-4px);
}

/* 5. Smooth focus ring */
.input {
  outline: 2px solid transparent;
  outline-offset: 2px;
  transition: outline-color 0.15s ease;
}
.input:focus { outline-color: #1572B6; }

/* 6. Expand/collapse height (needs known max-height) */
.panel { max-height: 0; overflow: hidden; transition: max-height 0.4s ease; }
.panel.open { max-height: 500px; }

/* 7. Accessibility: respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; }
}

📋 Summary

  • Put transition on the element, not the trigger state (:hover).
  • transition: property duration timing delay — all four parts.
  • Multiple transitions: comma-separate them.
  • Avoid transition: all — list specific properties.
  • Best performers: transform and opacity — compositor only, 60fps.
  • Good: color, background-color, box-shadow — repaint only.
  • Avoid for animation: width, height, top, left, margin — trigger reflow.
  • UI durations: 100–200ms micro, 200–400ms element changes, 400–600ms section transitions.
  • Always add @media (prefers-reduced-motion: reduce) override.

Frequently Asked Questions

Why does my transition not work? +

Most common causes: (1) transition is on the :hover state instead of the element itself — the transition must be on the base element. (2) The property is not animatable — display, visibility, and content cannot transition. (3) display: none to display: block cannot be transitioned — use opacity + visibility or max-height instead. (4) The starting and ending values are the same. Check all four in DevTools.

Can I transition display: none? +

display is not animatable — it switches instantly. Use this pattern instead: opacity: 0; visibility: hidden; pointer-events: none;opacity: 1; visibility: visible; pointer-events: auto; with transition: opacity 0.3s ease, visibility 0.3s ease. The visibility transition keeps the element in layout but invisible, while pointer-events: none prevents clicks. Modern CSS also supports @starting-style for transitioning from display: none in Chrome 116+.

What is the difference between transitions and animations? +

Transitions require a trigger — a state change (hover, focus, class toggle). They go from A to B once. Animations (@keyframes) run independently — they can loop, run automatically on load, go through multiple steps (A→B→C→D), and play in reverse. Use transitions for interactive state changes (hover effects, UI feedback). Use animations for autonomous motion (loading spinners, entrance effects, looping decorative elements).

Why does ease-out feel better than ease-in for most UI animations? +

Objects in the physical world decelerate as they arrive — they don't slam into their final position. ease-out (fast start, slow end) mimics this natural deceleration, which feels more physical and comfortable. ease-in (slow start, fast end) feels like something flying out of frame — better for elements leaving the screen. For most UI elements entering the viewport or revealing themselves: use ease-out. For elements departing: use ease-in.