CSS: Transitions
Smoothly animate changes between CSS property values.
transition-property
transition-property specifies which CSS property should be animated when it changes. Setting it to all transitions every property that changes, but targeting a specific property is more performant.
transition-property
Always place transition on the base element rule, not the :hover rule. If you place it on :hover, the transition only plays going in, not coming back.
- transition-property: background-color: only animate the background colour
- transition-property: all: animate every property that changes (use with caution)
- transition-property: none: disables transitions
- Place transition on the base element, not on :hover
- Only properties with numeric or colour values can be transitioned, display cannot be transitioned
transition-property
Only the background-color transitions, other properties change instantly.
.btn {
background-color: steelblue;
color: white;
padding: 10px 24px;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
/* transition is on the base rule */
transition-property: background-color;
transition-duration: 0.3s;
}
.btn:hover {
background-color: crimson;
}transition-duration
transition-duration sets how long the transition takes to complete. Values are in seconds (s) or milliseconds (ms). Without a duration, transitions are instant.
transition-duration
Most UI transitions should be between 150ms and 400ms. Too short feels abrupt; too long feels sluggish. 200–300ms is the sweet spot for hover effects.
- 0.2s or 200ms: fast; good for small hover effects like colour changes
- 0.3s or 300ms: medium; good for button and card interactions
- 0.5s or 500ms: slow; good for modals or page-level transitions
- 1s is usually too slow for interactive UI elements
- Both s and ms units are valid: 0.3s and 300ms are identical
transition-duration
Hover each box to see how duration affects the speed of the transition.
.box {
background: steelblue;
color: white;
padding: 12px 20px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition-property: background-color;
}
.fast { transition-duration: 0.15s; }
.medium { transition-duration: 0.3s; }
.slow { transition-duration: 0.6s; }
.box:hover { background-color: crimson; }transition-timing-function
transition-timing-function controls the speed curve of the transition: how the animation accelerates and decelerates over its duration.
transition-timing-function
The timing function shapes how the transition feels. ease is the most natural for most UI interactions; linear is best for continuous loops like spinners.
- ease: slow start, fast middle, slow end (default)
- linear: constant speed from start to finish
- ease-in: starts slow, ends fast
- ease-out: starts fast, ends slow (feels most natural for hover-out)
- ease-in-out: slow start and end, fast middle (symmetric)
- cubic-bezier(x1,y1,x2,y2): define a custom curve for precise control
timing functions
Hover the container to see how each timing function moves the ball differently.
.track {
background: #f0f4f8;
border-radius: 4px;
margin-bottom: 8px;
padding: 8px;
overflow: hidden;
}
.ball {
display: inline-block;
background: steelblue;
color: white;
padding: 6px 14px;
border-radius: 4px;
font-size: 0.85rem;
transition-property: transform;
transition-duration: 0.8s;
}
.ease { transition-timing-function: ease; }
.linear { transition-timing-function: linear; }
.ease-in-out { transition-timing-function: ease-in-out; }
.track:hover .ball { transform: translateX(200px); }transition-delay
transition-delay sets a waiting period before the transition starts. The property changes immediately in the browser's internal state: only the visual animation is delayed.
transition-delay
transition-delay is useful for staggering multiple elements so they animate one after another. A negative delay starts the animation partway through.
- transition-delay: 0.2s: wait 200ms before the transition begins
- transition-delay: 0s: no delay, transition starts immediately (default)
- negative delay: e.g. -0.1s starts the animation already 100ms in
- Stagger items by giving each a slightly larger delay to create a cascade effect
transition-delay
Hover the container, each item starts its transition slightly later, creating a stagger effect.
.group {
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px;
background: #f0f4f8;
border-radius: 6px;
}
.item {
background: steelblue;
color: white;
padding: 8px 16px;
border-radius: 4px;
transition-property: transform;
transition-duration: 0.3s;
transition-timing-function: ease-out;
}
.d0 { transition-delay: 0s; }
.d1 { transition-delay: 0.1s; }
.d2 { transition-delay: 0.2s; }
.d3 { transition-delay: 0.3s; }
.group:hover .item { transform: translateX(30px); }Transition Shorthand
The transition shorthand combines all four transition properties in one declaration. The order is: property duration timing-function delay.
transition shorthand
The shorthand is the most common way to write transitions. Only property and duration are required: timing-function defaults to ease and delay defaults to 0s.
- transition: background-color 0.3s: property and duration only (timing defaults to ease)
- transition: background-color 0.3s ease-out: with timing function
- transition: background-color 0.3s ease-out 0.1s: with delay
- transition: all 0.3s ease: transitions every changing property
transition shorthand
All transition properties written in a single declaration.
.btn {
background: steelblue;
color: white;
padding: 10px 24px;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
/* property | duration | timing | delay */
transition: background-color 0.3s ease-out;
}
.btn:hover {
background: #1d4ed8;
}Animating Multiple Properties
You can transition multiple properties at once by separating them with commas. Each property can have its own duration, timing function, and delay.
Multiple transitions
Use a comma-separated list to give each property its own transition settings. This is more precise and performant than transition: all.
- Separate multiple transitions with commas
- transition: background 0.3s, transform 0.2s: background takes 0.3s, transform takes 0.2s
- Each property can have a different duration, timing, and delay
- Avoid transition: all in production, it can accidentally animate properties you did not intend
Multiple properties
Background, transform, and box-shadow each transition at their own speed.
.btn {
background: steelblue;
color: white;
padding: 12px 28px;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
transition:
background 0.3s ease,
transform 0.15s ease,
box-shadow 0.3s ease;
}
.btn:hover {
background: #1d4ed8;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.2);
}Performance Considerations
Not all CSS properties are equal when it comes to animation performance. Transitioning the wrong properties can cause the browser to recalculate the layout on every frame, leading to jank and dropped frames.
Performance: use transform and opacity
The browser rendering pipeline has three stages: layout, paint, and composite. Only transform and opacity run entirely in the composite stage, making them GPU-accelerated and smooth.
- transform and opacity: GPU-accelerated; do not trigger layout or paint; always 60fps
- color, background-color, box-shadow: trigger repaint only; acceptable for most UI
- width, height, top, left, margin, padding: trigger full layout recalculation on every frame; avoid animating these
- Use transform: translateX() instead of left/right to move elements
- Use transform: scale() instead of width/height to resize elements
- will-change: transform: hints to the browser to promote the element to its own GPU layer in advance
Performance
Both visually expand on hover, but only the second is GPU-accelerated.
.bad, .good {
background: steelblue;
color: white;
padding: 12px;
border-radius: 4px;
margin-bottom: 10px;
cursor: pointer;
}
/* Avoid: causes layout recalc on every frame */
.bad { width: 200px; transition: width 0.3s ease; }
.bad:hover { width: 300px; }
/* Prefer: GPU-accelerated, no layout recalc */
.good { transition: transform 0.3s ease; }
.good:hover { transform: scaleX(1.2); transform-origin: left; }Common Transition Patterns
These patterns appear in almost every project. They are simple but make a significant difference in how polished and interactive a UI feels.
Common Patterns
These are the most frequently used transition recipes. Each one targets GPU-friendly properties for smooth performance.
- Button lift: transform: translateY(-2px) + increased box-shadow on :hover
- Fade in/out: opacity: 0 to opacity: 1 with transition: opacity 0.3s
- Colour swap: transition: background-color 0.2s, color 0.2s on links and buttons
- Grow on hover: transform: scale(1.05) on cards or images
- Slide in: transform: translateX(-100%) to translateX(0) for drawers and menus
Card lift pattern
Card lifts and casts a deeper shadow on hover, a common card interaction.
.card {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
max-width: 260px;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 24px rgba(0,0,0,0.14);
}
.card h3 { margin: 0 0 8px; font-size: 1.1rem; }
.card p { margin: 0; color: #64748b; font-size: 0.9rem; }Fade with opacity
Element fades out on hover using opacity transition.
.fade-box {
background: steelblue;
color: white;
padding: 16px 24px;
border-radius: 6px;
cursor: pointer;
transition: opacity 0.3s ease;
}
.fade-box:hover { opacity: 0.3; }Knowledge Check
1. Where should the transition property be placed?
2. What does transition-duration: 0.3s mean?
3. Which timing function starts slow, speeds up, then slows down at the end?
4. What does transition-property: all do?
5. Why are transform and opacity preferred for transitions over top/left or width?
6. What does transition-delay: 0.2s do?