Transitions & Animations
@keyframes, transition shorthand, timing functions, will-change, and animation performance — bring your UI to life.
What Are CSS Transitions
A CSS transition smoothly interpolates a property's value from one state to another over a given duration. Instead of changes happening instantly (e.g., a button changing color on hover), the browser animates between the two values.
Transitions require a trigger — a pseudo-class (:hover, :focus), a class change via JavaScript, or any state change that alters a property value.
.button { background: #6c8cff; transition: background 0.3s ease; } .button:hover { background: #4a6cf7; /* smoothly transitions to this */ }
Which Properties Can Be Transitioned?
Not every CSS property is animatable. The property must have identifiable intermediate values. Here are common transitionable properties:
| Category | Properties |
|---|---|
| Color | color, background-color, border-color, outline-color, box-shadow color |
| Layout | width, height, max-width, max-height, padding, margin |
| Transform | transform (translate, rotate, scale, skew) |
| Opacity | opacity |
| Typography | font-size, letter-spacing, word-spacing, line-height |
| Border | border-width, border-radius, border-color |
| Position | top, right, bottom, left |
| Not Animatable | display, font-family, position, float, content |
transition-property
Specifies which CSS properties should be transitioned. You can target specific properties or use all.
/* Single property */ transition-property: background-color; /* Multiple properties */ transition-property: background-color, transform, opacity; /* All transitionable properties */ transition-property: all; /* None (disable transitions) */ transition-property: none;
(color snaps)
(both smooth)
transition-property: all is convenient but can cause unexpected transitions on properties you didn't intend. Prefer listing specific properties for production code.
transition-duration
How long the transition takes. Values in seconds (s) or milliseconds (ms). A duration of 0s means no transition (instant).
transition-duration: 0.3s; /* 300 milliseconds */ transition-duration: 300ms; /* same thing */ transition-duration: 1s; /* one second */ /* Different durations per property */ transition-property: background, transform; transition-duration: 0.3s, 0.6s;
Hover the area above to see all bars expand at different speeds.
transition-timing-function
Controls the acceleration curve of the transition — how the intermediate values are distributed over time.
Built-in Keywords
| Value | Behavior | cubic-bezier Equivalent |
|---|---|---|
ease | Slow start, fast middle, slow end (default) | cubic-bezier(0.25, 0.1, 0.25, 1) |
linear | Constant speed throughout | cubic-bezier(0, 0, 1, 1) |
ease-in | Slow start, accelerates | cubic-bezier(0.42, 0, 1, 1) |
ease-out | Fast start, decelerates | cubic-bezier(0, 0, 0.58, 1) |
ease-in-out | Slow start and end | cubic-bezier(0.42, 0, 0.58, 1) |
Hover to see all dots race. The purple "bouncy" dot uses cubic-bezier(0.68, -0.55, 0.27, 1.55).
steps()
The steps() function divides the transition into equal jumps — useful for sprite sheet animations or typewriter effects.
/* 5 discrete steps */ transition-timing-function: steps(5, jump-end); /* Common sprite animation */ .sprite { width: 64px; background: url('sheet.png'); animation: walk 0.6s steps(8) infinite; }
transition-delay
Waits a specified time before the transition starts. Can be positive or negative.
transition-delay: 0.2s; /* wait 200ms then start */ transition-delay: -0.3s; /* start 300ms into the transition */ /* Staggered transitions on multiple properties */ transition-property: opacity, transform; transition-duration: 0.4s, 0.4s; transition-delay: 0s, 0.15s; /* transform starts later */
Hover to see the wave effect created by staggered delays.
Transition Shorthand
The transition shorthand combines all four sub-properties in one declaration:
/* transition: property duration timing-function delay */ transition: background-color 0.3s ease 0s; /* Multiple transitions */ transition: background 0.3s ease, transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease 0.1s; /* Shortcut for all properties */ transition: all 0.3s ease;
Practical Transition Examples
Hover Card
This card lifts up, gets a stronger shadow, and highlights its border on hover using CSS transitions.
What Are CSS Animations
CSS animations differ from transitions in key ways:
| Feature | Transitions | Animations |
|---|---|---|
| Trigger | Requires state change (:hover, class toggle) | Runs automatically (or on class add) |
| Keyframes | Only 2 states (from → to) | Multiple keyframes (0%, 25%, 50%…) |
| Looping | Cannot loop | Can loop infinitely |
| Direction | Forward only | Normal, reverse, alternate |
| Complexity | Simple state transitions | Complex multi-step sequences |
@keyframes
The @keyframes rule defines the stages of an animation. You give it a name and specify what should happen at each point in time.
/* Simple from/to */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* Percentage keyframes */ @keyframes bounce { 0% { transform: translateY(0); } 40% { transform: translateY(-30px); } 60% { transform: translateY(-15px); } 80% { transform: translateY(-5px); } 100% { transform: translateY(0); } } /* Multiple properties */ @keyframes slideInFade { 0% { opacity: 0; transform: translateX(-40px); } 100% { opacity: 1; transform: translateX(0); } }
Animation Properties
Apply a keyframe animation to an element with these properties:
.element { animation-name: bounce; /* name of @keyframes */ animation-duration: 0.8s; /* how long */ animation-timing-function: ease; /* acceleration curve */ animation-delay: 0.2s; /* wait before starting */ animation-iteration-count: 3; /* how many times */ animation-direction: alternate; /* forward then backward */ animation-fill-mode: forwards; /* keep end state */ animation-play-state: running; /* running or paused */ }
animation-iteration-count
animation-iteration-count: 1; /* play once (default) */ animation-iteration-count: 3; /* play 3 times */ animation-iteration-count: infinite; /* loop forever */ animation-iteration-count: 2.5; /* play 2.5 times, stop mid-animation */
animation-direction
animation-fill-mode
Controls what styles apply to the element before the animation starts and after it ends.
| Value | Before Animation | After Animation |
|---|---|---|
none | Element's own styles | Element's own styles (default) |
forwards | Element's own styles | Keeps last keyframe's styles |
backwards | Applies first keyframe (during delay) | Element's own styles |
both | Applies first keyframe | Keeps last keyframe's styles |
After the animation ends: "none" snaps back, "forwards" stays at the end position.
animation-play-state
Pauses or resumes an animation. Commonly used with :hover to let users control animations.
.spinner { animation: spin 2s linear infinite; } .spinner:hover { animation-play-state: paused; }
Hover to pause the rotation.
Animation Shorthand
/* animation: name duration timing delay count direction fill-mode play-state */ animation: bounce 0.8s ease-in-out 0.2s infinite alternate forwards running; /* In practice, most values are optional */ animation: fadeIn 0.5s ease; animation: spin 1s linear infinite; animation: slideUp 0.4s ease forwards; /* Multiple animations on one element */ animation: fadeIn 0.5s ease, slideUp 0.6s ease 0.1s;
Practical Animation Examples
Loading Spinners
Ring
Dots
Pulse
Attention Animations
Hover each box to trigger the animation.
Gradient Animation
Typing Effect
Performance
Not all CSS properties are equal in animation performance. The browser rendering pipeline has three stages: Layout → Paint → Composite. Animating properties that only trigger compositing is the cheapest.
Property Cost Table
| Tier | Properties | Cost |
|---|---|---|
| Composite Only | transform, opacity | Cheapest — GPU accelerated, no repaint |
| Paint | color, background, box-shadow, border-radius | Medium — repaint but no layout |
| Layout | width, height, padding, margin, top, left, font-size | Expensive — triggers full layout recalculation |
transform and opacity. These are the only properties that can be handled entirely by the GPU compositor without triggering layout or paint.
will-change
/* Hint the browser to prepare for animation */ .animated-element { will-change: transform, opacity; } /* Apply on hover intent, remove when done */ .card { transition: transform 0.3s ease; } .card:hover { will-change: transform; transform: translateY(-4px); }
will-change to too many elements wastes GPU memory. Only use it on elements that actually animate, and consider removing it after the animation completes.
prefers-reduced-motion
Some users experience motion sickness or have vestibular disorders. The prefers-reduced-motion media query lets you respect their system preference.
/* Approach 1: Remove animations for users who prefer reduced motion */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } /* Approach 2: Only add animations for users who are OK with motion */ .element { opacity: 1; /* default: no animation */ } @media (prefers-reduced-motion: no-preference) { .element { animation: fadeIn 0.5s ease; } }
Gotchas
display: none to display: block happens instantly — no transition. Workaround: use opacity and visibility together, or use @starting-style (modern CSS).
height: auto cannot be transitioned. The browser doesn't know what numeric value "auto" resolves to ahead of time. Workaround: transition max-height from 0 to a large value, or use CSS Grid's grid-template-rows: 0fr → 1fr trick.
.no-transition class to <body> and remove it after load, or defer the class that triggers transitions.
@keyframes names are global. If two stylesheets define @keyframes fadeIn, the last one wins. Use unique, prefixed names (e.g., myApp-fadeIn) or CSS @layer to avoid conflicts.
transition on the base element, not on :hover. If you put it on :hover, the return transition (mouse leave) won't be smooth.
/* WRONG — transition only applies during hover */ .btn:hover { background: red; transition: background 0.3s; } /* RIGHT — transition applies in both directions */ .btn { transition: background 0.3s; } .btn:hover { background: red; }
Pro Tips
top/left (triggers layout), use transform: translate() (composite only). Same visual effect, dramatically better performance.
- Snappy:
cubic-bezier(0.4, 0, 0.2, 1)— Material Design standard - Bouncy:
cubic-bezier(0.68, -0.55, 0.27, 1.55) - Swift out:
cubic-bezier(0.55, 0, 0.1, 1)
animation-delay with nth-child() for pure-CSS staggering without JavaScript:
.item { animation: fadeInUp 0.5s ease both; } .item:nth-child(1) { animation-delay: 0.0s; } .item:nth-child(2) { animation-delay: 0.1s; } .item:nth-child(3) { animation-delay: 0.2s; } .item:nth-child(4) { animation-delay: 0.3s; } /* Or use custom properties for dynamic staggering */ .item { animation-delay: calc(var(--index) * 0.1s); } /* <div class="item" style="--index:0"> */ /* <div class="item" style="--index:1"> */
height: auto can't be transitioned, use max-height with a large value:
.accordion-body { max-height: 0; overflow: hidden; transition: max-height 0.4s ease; } .accordion.open .accordion-body { max-height: 500px; /* larger than content */ } /* Modern alternative with Grid: */ .accordion-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.4s ease; } .accordion.open .accordion-body { grid-template-rows: 1fr; }
- Micro-interactions (hover, focus): 100–200ms
- Small movements (tooltips, dropdowns): 200–300ms
- Medium movements (modals, panels): 300–500ms
- Large movements (page transitions): 500–800ms
- Anything over 1s feels sluggish for UI interactions