CSS Animation Generator
Build a CSS @keyframes animation visually — add percentage steps, set transform, opacity and background at each one, tune the timing, watch it loop live, and copy the exact keyframes block, the animation: shorthand and a prefers-reduced-motion fallback. Everything runs in your browser.
Need a custom curve? Build one with the cubic-bezier() generator → and paste the value in.
@keyframes block
animation shorthand
Reduced-motion fallback
For visitors whose device is set to reduce motion. The animation still runs — it just finishes instantly, so the element jumps to its final state rather than never running. Paste this after the rule it overrides.
The link carries your animation settings in the URL; nothing is uploaded.
A field holds something a link cannot carry ({ } ; < >, a value over 160 characters, or more than 24 steps), so the address bar has no link for this animation.
Nothing you build is uploaded. The keyframes, the preview and both CSS strings are computed on your own device with plain JavaScript — the page's only network call is one anonymous, self-hosted page-view count, and the page keeps working offline.
Offline later: once you have opened this page with a connection, it reopens and works with none — your browser keeps a copy. Not saved: the very first Polyatic page you ever open (open it again while online), and any page or link you have never opened.
The two halves of a CSS animation
A CSS animation is always two pieces working together. The @keyframes block names a sequence and describes what the element looks like at each point along the way — a list of percentage steps from 0% to 100%, each holding the styles the element should show at that moment. On its own it moves nothing. The animation shorthand is the line you put on the element to play that sequence: it points at the keyframes by name and sets how long the run takes, its easing, any delay, how many times it repeats, its direction and its fill-mode. This tool builds and copies both — paste the @keyframes into your stylesheet once, then drop the animation: line onto every element that should use it.
The shorthand order, and why values disappear
The generator writes the shorthand in the order the MDN reference documents:
animation: <duration> <timing-function> <delay>
<iteration-count> <direction> <fill-mode> <name>;
The two time values lead because the browser has no other way to tell them apart: it assigns the first <time> it reads to animation-duration and the second to animation-delay. Everything after is matched by keyword. To keep the line short and honest, any value that already equals its CSS default is left out — timing-function: ease, delay: 0s, iteration-count: 1, direction: normal and fill-mode: none all vanish when unchanged. Duration and the name are always printed. That is why a plain one-second animation collapses to just animation: 1s pop-in; — a complete, valid rule with every other property sitting at its default.
A worked example
The default here is a "pop in": the element starts small and transparent, overshoots slightly past full size, then settles. Its output is:
@keyframes pop-in {
0% { transform: scale(.6) translateY(1.25rem); opacity: 0; }
60% { transform: scale(1.05) translateY(0); opacity: 1; }
100% { transform: scale(1); }
}
.card { animation: 1s pop-in; }
The 60% step pushing scale past 1 is what gives the motion its bounce — the element springs a touch too far and relaxes back at 100%. Change duration to taste and, if the element should stay put at the end, add forwards to the fill-mode so it holds the final keyframe instead of snapping back to its base styles.
What each option changes
| Property | What it controls | Default |
|---|---|---|
| duration | how long one run takes | 0s (required for motion) |
| timing-function | the speed curve within each segment | ease |
| delay | wait before the first run starts | 0s |
| iteration-count | how many times it repeats (infinite = forever) | 1 |
| direction | forward, reversed, or ping-pong (alternate) | normal |
| fill-mode | styles held before/after the run | none |
One subtlety worth knowing: the timing-function applies to each interval between keyframes, not the whole run. With three or more steps the easing restarts at every one, so an ease-in-out across many stops can read as a series of little accelerations rather than one smooth curve. For a single, exact easing shape use a two-step (0%/100%) animation, or reach for a custom cubic-bezier() curve.
Motion preferences, and the third block
Every big operating system has a switch for this — macOS calls it Reduce motion, and Windows, iOS, Android and the major Linux desktops each have their own equivalent. Flip it and the browser starts answering yes to the CSS media query prefers-reduced-motion: reduce, which is the only signal your stylesheet ever gets. People turn it on as an ordinary preference, and people turn it on for real medical reasons they are under no obligation to explain to a website; the query does not tell you which, so the honest reading is that it means exactly what it says. It matters most for the case this generator puts one click away: an iteration-count of infinite keeps moving until the element is removed from the page, so there is never a moment at which it stops.
The block this page emits is deliberately not animation: none. Cancelling an animation leaves the element wearing whatever its base rule says, which is fine for decoration and wrong for an entrance: the default pop-in starts at opacity: 0, and an element whose visibility came entirely from the keyframes would simply never appear. So the fallback collapses the run to an instant instead — animation-duration: 0.01ms with animation-iteration-count: 1. That is under a thousandth of a single 60fps frame, so nothing is ever painted mid-flight, and the animation still finishes: the element jumps to its final state rather than never running. Only those two longhands are overridden, so your own rule keeps supplying the name, the easing and everything else — which is why the block has to be pasted after it.
Two honest edges. With the default fill-mode: none the element does not literally hold the 100% keyframe afterwards — it returns to its ordinary, un-animated styles, which for a normal entrance is the same picture you wanted; if you need the last keyframe held, set fill-mode to forwards and the fallback holds it too. And if you set a delay, the generator adds animation-delay: 0s to the block, because a two-second wait in front of an instant animation is still two seconds of waiting — and with fill-mode: backwards or both it would be two seconds of the element sitting in its 0% state. This page takes its own advice in the bluntest way available to it: the shared stylesheet stops every animation under prefers-reduced-motion: reduce, so with the setting on, the preview above holds still. That works here because the demo box is fully visible without its animation — which is exactly what you cannot assume about the CSS you copy.
Sharing an animation as a link
Every change you make is written into the address bar, so a reload brings back exactly what you built and Copy link hands somebody else the same animation. An untouched preset keeps a short, readable link — #preset=bounce opens the bounce, and the other names are pop-in, fade-in, slide-up, pulse, shake, spin and color-shift. Anything you have edited becomes #a= followed by the settings as a small piece of JSON in base64url: the name, duration, delay, easing, iteration count, direction, fill mode and every keyframe step with its three values. A five-step animation comes to roughly 200–300 characters. Everything sits after the #, which browsers never send to a server, so the link is read by the page on the recipient's device and by nothing else. A link that has been cut short or mangled opens the default pop-in instead of an error. The reduced-motion selector is not part of the link; it describes your stylesheet, not the animation.
Honest limits
This is a v1 with a bounded property set: each keyframe step takes one transform, one opacity and one background value, and nothing else. That trio is deliberate — transform and opacity are the two cheapest things to animate (the browser can composite them on the GPU without re-running layout or paint), and together with background they cover the overwhelming majority of interface motion: fades, slides, pops, spins and colour pulses. CSS itself animates far more — color, box-shadow, clip-path, width, filter and dozens of others — and none of those are offered here. When you need one, copy the generated @keyframes and add the extra declaration by hand; the block is plain CSS you own. Two more real caveats: animating layout properties like width or top (which this tool avoids) can stutter on low-end devices, and a stack of long infinite animations keeps the compositor busy, so ship the fewest steps that read right. For the shapes and colours you animate, pair this with the Box-Shadow Generator, the Gradient Generator, the Transform Generator and the cubic-bezier() Generator.