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.
/* 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;
}
The Four Transition Properties
transition-property
Which CSS properties to animate. Only changes to listed properties will transition.
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 */
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
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.
/* 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
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; }
transition 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:
/* ✅ 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
/* 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; }
}
Animate the Cheap Properties, or the UI Stutters
Not all properties are equal to animate. Changing width, height, top, or margin forces the browser to re-run layout for the whole page on every frame — janky on phones. Two properties, transform and opacity, are handled by the GPU and skip layout entirely:
/* ❌ triggers layout every frame */
.box:hover { width: 320px; margin-top: 20px; }
/* ✅ GPU-friendly, buttery 60fps */
.box:hover { transform: scale(1.05) translateY(-4px); }
transition: all
It quietly animates every property that changes — including expensive ones you didn't intend — and can cause surprise reflows. Name the properties you actually want:
transition: transform 0.2s ease, opacity 0.2s ease;
You can only transition between two defined values, so you can't transition to/from height: auto — a common frustration. Use max-height, transform: scaleY, or the modern grid-template-rows trick instead.
🏋️ Practical Exercise
- Add a
transitionon a button’s background color. - Change the duration and timing function.
- Transition multiple properties at once.
- Add a
transition-delay. - Transition a
transformon hover.
🔥 Challenge Exercise
Build a button whose hover effect transitions background color, scale (transform), and box-shadow together over 0.3s with an ease timing function. Explain which properties can be transitioned and why transitioning transform/opacity is more performant than width/top.
📋 Summary
- Put
transitionon 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:
transformandopacity— 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.
Interview Questions
- What does the
transitionproperty do? - What four sub-properties make up the
transitionshorthand? - Which CSS properties are cheap to animate, and why?
- What is the difference between a transition and an animation?
- Can you transition
display: none?
Related Topics
FAQ
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.
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+.
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).
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.

