Polyatic CSS Animation Generator
All tools

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.

Start from a preset
animation-name
duration1s
delay0s
timing-function

Need a custom curve? Build one with the cubic-bezier() generator → and paste the value in.

iteration-count
direction
fill-mode
Keyframe steps
Live preview

@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.

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

PropertyWhat it controlsDefault
durationhow long one run takes0s (required for motion)
timing-functionthe speed curve within each segmentease
delaywait before the first run starts0s
iteration-counthow many times it repeats (infinite = forever)1
directionforward, reversed, or ping-pong (alternate)normal
fill-modestyles held before/after the runnone

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.