CSS Animation Generator
Build a @keyframes timeline, tune the animation shorthand and preview it live, then copy working CSS — entirely on this device.
Paused — your system asks for reduced motion. Play to preview.
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}.element {
animation: 1.5s ease-in-out infinite pulse;
}Gate decorative animation behind @media (prefers-reduced-motion: no-preference) so it only runs when the visitor hasn't asked for less motion.
Do it offline:$ cat animation.css >> styles.css
How to use it
- Name the animation, then add keyframes to the timeline and give each an offset from 0 to 100%.
- Inside a keyframe, add property and value rows — transform, opacity, color, filter — and the preview restyles as you type.
- Tune the shorthand: duration, timing-function, delay, iteration-count or infinite, direction and fill-mode.
- Toggle the reduced-motion guard, then copy both blocks, download animation.css, or share the timeline as a link.
Lay out a @keyframes timeline, tune the animation shorthand, and a real element plays it back live — the preview runs the CSS you copy, not a drawing of it. Six presets cover the everyday moves, the reduced-motion guard keeps decorative motion optional, and the keyframe block and shorthand come out ready to paste, all on this device.
How it works
A CSS animation has two halves. The @keyframes rule is a timeline: each offset from 0% to 100% holds a set of property values, and the browser interpolates between them. The animation shorthand binds that timeline to an element and says how it plays — duration, timing-function, delay, iteration-count, direction and fill-mode — with the @keyframes name last. css-animations-1 specifies both, and the output follows that shorthand order.
Animation can trigger motion sickness, so the prefers-reduced-motion media feature reports when someone has asked their system for less of it. Gate decorative motion behind @media (prefers-reduced-motion: no-preference) and it runs only for visitors who haven't; the guard toggle writes that wrapper, and the preview itself mounts paused under reduce with a visible Play control.
Examples
Default timeline — pulse@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}Three keyframes at 0%, 50% and 100% drive one scale bounce. The tool ships this pulse so the stage is never blank, and it loops because iteration-count is infinite.
Preset: slide-in-left.element {
animation: 0.5s ease-out both slide-in-left;
}Duration and name always print; every value left at its CSS initial drops out, so iteration-count 1, delay 0 and direction normal vanish while the non-default ease-out and fill-mode both remain.
Infinite alternate — blink.element {
animation: 750ms 250ms infinite alternate blink;
}Milliseconds serialize as typed, infinite replaces the count, and alternate runs every other pass backwards; the 250ms delay sits between the two times, each keeping its unit.
Reduced-motion guard wrapper@media (prefers-reduced-motion: no-preference) {
.element {
animation: 0.6s forwards reveal;
}
}The guard wraps the rule in @media (prefers-reduced-motion: no-preference), so it plays only for visitors who haven't requested less motion.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
An empty timeline is still valid CSS
A keyframe list with no declarations serializes as @keyframes noop {} — inert but legal. The preview simply holds still while you build.
Empty keyframes are scaffolding, not output
Add an offset but no properties and the row stays in the editor, yet the serialized @keyframes skips it — only steps that change something reach the copied CSS.
Play state never travels with the copy
Play, Pause and Replay drive the preview only. animation-play-state is never written to the shorthand, so nothing you paste elsewhere ships frozen on paused.
Duplicate offsets both survive, in order
Two keyframes at the same percent both print, keeping the order you authored them — the later declaration wins, exactly as the cascade resolves them.
Vendor prefixes pass through untouched
A hand-typed -webkit-transform is accepted and printed verbatim; the tool never adds or strips prefixes, so what you enter is what you copy.
A zero-second duration still emits
0s means an instant jump with no tween, but duration and the name always anchor the shorthand, so the animation line is never left ambiguous.
Common errors
`infinite` is a reserved CSS keyword — pick another @keyframes name so the animation shorthand stays unambiguous.- Reserved words like infinite or none can't name the animation, because the shorthand prints the name last and a keyword there would be read as a longhand. The diagnostic quotes your word.
A CSS value can't contain `;` `{` `}` `<` `>` `\` or `/* */`, and its parentheses must balance — this keeps the copied CSS from breaking out of the rule.- A value can't carry characters that would end the declaration or the rule, so anything with those tokens or an unbalanced paren is refused rather than copied into breakable CSS.
`1x` isn't a valid @keyframes name — start with a letter, `_` or `-`, then use letters, digits, `-` or `_`.- An @keyframes name is a CSS identifier: it opens with a letter, underscore or hyphen, never a digit. The message repeats your token and names the shape it expects.
The value is longer than the 128-character limit for this tool — shorten it.- Values past 128 characters are refused whole, never truncated — a silent cut could change the meaning. Any single transform, color or filter fits well under the cap.
FAQ
How do I make a CSS animation loop forever?
Set iteration-count to infinite, or flip the Infinite control. The shorthand then prints infinite in place of a number and the animation repeats until the element leaves the page. The default pulse loops this way.
How do I respect a visitor's reduced-motion setting?
Wrap the rule in @media (prefers-reduced-motion: no-preference) so it plays only when the visitor hasn't asked their system for less motion. The guard toggle adds that wrapper, and the preview starts paused under reduce.
Is @keyframes different from the animation shorthand?
Yes. @keyframes defines what changes at each point on the timeline; the shorthand on the element sets how that timeline plays and names the @keyframes. You copy both to move an animation into a stylesheet.
Keyboard shortcuts
- ⌘/Ctrl + C
- Copy all CSS while nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy all CSS even over an active selection.
- ⌘/Ctrl + S
- Download the current CSS as animation.css.
- ? or ⌘/Ctrl + /
- Open the keyboard-help dialog.
- t
- Cycle the color theme.
- Esc
- Close the topmost dialog.