Pick from 16 named animation presets covering the patterns developers actually need: fade-in/out, slide directions, scale, bounce, shake, pulse, spin, flip, wobble, swing, zoom. Each preset comes with sensible defaults you can tune — duration, easing, delay, iteration count and direction. The generator outputs the complete @keyframes rule plus the animation shorthand, ready to paste.
infinite + alternate for loaders and breathing effects. Use 1 + forwards for entrances that stick.@keyframes rule and the animation shorthand. Drop both into your stylesheet.Easing is the single biggest factor in whether an animation feels professional or amateur. Quick guide:
| Easing | Feel | Best For |
|---|---|---|
ease-out | Fast in, decelerates to stop | Entrances, things arriving on screen |
ease-in | Slow start, accelerates | Exits, things leaving the screen |
ease-in-out | Symmetric, soft on both ends | Loops, breathing/pulse effects, state changes |
linear | Constant speed, mechanical | Spinners, marquees, anything continuous |
| spring (overshoot) | Goes past target then settles | Pop-ins, attention-getters, success states |
| elastic | Pulls back then snaps forward | Playful UI, kid/game contexts |
steps(N) | Discrete jumps, no interpolation | Sprite sheets, typewriter effects, frame-by-frame |
The steps() timing function divides an animation into discrete jumps instead of smooth interpolation. This is essential for two use cases: sprite sheet animation and typewriter-style reveals.
When animating a sprite sheet, you shift background-position across the sheet in discrete frames. The most common bug — frames stacking on top of each other — happens when the step count, sheet width and background-position shift are out of sync.
The formula is: steps(N) where N = number of frames, and the background-position shift = N × frame width. If your sprite has 10 frames at 64px each, you need steps(10) and shift -640px:
.sprite {
width: 64px;
height: 64px;
background: url('spritesheet.png') left center;
animation: play 0.8s steps(10) infinite;
}
@keyframes play {
to { background-position: -640px 0; }
}
The steps() function accepts a second parameter — the "jump term" — that controls where the step change happens:
| Jump Term | Behavior | Use For |
|---|---|---|
end (default) | Stays at start value, jumps at end of each step | Sprite sheets (most common) |
start | Jumps immediately at start of each step | Typewriter effects, countdowns |
jump-none | Neither first nor last value is skipped | When you need both the start and end keyframe visible |
jump-both | Pauses at both ends, adds an extra step | Looping sprite cycles that need a pause at each end |
The two shorthand keywords step-start and step-end are equivalent to steps(1, start) and steps(1, end) — useful for binary state flips like visibility toggling or clock-second ticking.
Common debugging checklist when sprite frames stack:
background-position end value equals frames × frame width exactly.steps(N) not linear — without steps the background slides smoothly and frames overlap.jump-none, the step count is N-1 (one fewer step since both endpoints are shown).to not from — animating from 0 to the negative total width.The Material Design guidelines and 20 years of UX research converge on a tight range. Anything outside it feels wrong:
The "perceived performance" rule: if you can't tell whether something animated or just snapped, the animation is too fast. If you're waiting for it to finish, it's too slow.
Common confusion. Quick rule:
transition when you have two clear states and something (hover, focus, class change) flips between them.animation with @keyframes when you need multiple steps, loops, or autonomy from a trigger.Modern browsers can animate any CSS property — but only some are GPU-accelerated. Animating the rest forces layout recalculation every frame and tanks performance, especially on mobile.
| Property | Performance | Equivalent |
|---|---|---|
transform | Excellent (GPU) | — |
opacity | Excellent (GPU) | — |
filter | OK (GPU, but heavier) | Use sparingly |
top, left, width, height | Poor (causes reflow) | Use transform: translate() / scale() |
margin, padding | Poor (causes reflow) | Wrap in a transformable element |
background-color | OK on small elements | Avoid on full-screen |
Every preset in this generator uses only transform and opacity for this reason. They'll run smoothly even on low-end mobile devices.
prefers-reduced-motionSome users get nauseous or distracted by animations. Browsers expose this preference via a media query. Always wrap decorative animations:
@media (prefers-reduced-motion: no-preference) {
.my-element {
animation: fadeIn 0.4s ease-out forwards;
}
}
This pattern means the animation only runs for users who haven't opted out. The element will still appear — just without motion. Skipping this is bad practice and in some jurisdictions an accessibility compliance issue.
CSS now supports scroll-driven animations natively with animation-timeline. Instead of time-based playback, animations progress as the user scrolls — no JavaScript needed.
There are two timeline types: scroll() ties progress to a scroll container's scroll position, and view() ties progress to an element's visibility within a scroll container (like Intersection Observer but declarative).
/* Fade in as element scrolls into view */
.scroll-reveal {
animation: fadeIn linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
The animation-range property controls when the animation starts and ends relative to the scroll. entry 0% means the element's leading edge enters the viewport; entry 100% means it's fully visible. Other range keywords include exit, contain and cover.
/* Scroll progress bar at the top of the page */
.progress-bar {
position: fixed;
top: 0; left: 0;
width: 100%; height: 4px;
background: linear-gradient(90deg, #7c6fff, #ff6fb0);
transform-origin: left;
animation: growWidth linear both;
animation-timeline: scroll(root);
}
@keyframes growWidth {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Scroll-driven animations are supported in Chrome 115+, Edge 115+ and Safari 18+. Firefox support is in progress behind a flag. Because the feature is purely progressive — if unsupported the element simply stays static — it's safe to ship today.
CSS animations are universal — supported in every browser since IE 10. No prefixes, no fallbacks needed. The only modern caveat is @property for animating custom property values, which needs Chrome 85+, Firefox 128+, Safari 16.4+.
A transition animates between two states triggered by something (hover, class change, focus). An animation runs autonomously using @keyframes that define multiple steps — it can loop, have multiple stages, and run without any trigger. Use transition for hover effects and state changes; use animation for entrances, loaders, attention-getters and looping effects.
Define @keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } } then apply animation: fadeIn 0.4s ease-out forwards to the element. The fade-in preset above outputs this exact pattern with tunable duration and easing. Add forwards so the element stays visible after the animation ends.
For most fade-ins use ease-out — it starts fast and decelerates, which feels natural for entrances. ease-in feels sluggish for entrances; linear feels mechanical. For exits, ease-in is correct because the user expects the element to "leave" quickly at the end.
Set animation-iteration-count: 1 (the default) and animation-fill-mode: forwards so the element keeps its final state after the animation ends. Without forwards, the element will snap back to its original state. The generator above defaults to this combination.
Most commonly because you're animating layout properties (width, height, top, left) instead of compositor-only properties (transform, opacity). Switch to transform and opacity for smooth 60fps animations. The performance table above shows which properties are safe.
Yes — comma-separate them: animation: fadeIn 0.4s ease-out, slideUp 0.4s ease-out 0.1s. Each can have its own duration, delay and easing. This is useful for compound effects like fade-in combined with slide-up.
Apply the animation property inside a :hover selector rather than the base element. The animation will restart each time the user hovers. Toggle "Trigger on hover" in the generator above to output this pattern automatically.
Want to combine animations with other CSS effects? Pair this with our CSS Filters for blur/glow transitions, or box-shadow for animated elevation effects.