CSS Keyframes Builder

Build @keyframes animations on a visual timeline, preview them live, and copy the CSS with a reduced motion guard.

Presets
Timeline

Click the empty timeline to add a stop. Drag a stop to move it, or focus one and use the arrow keys, Shift and an arrow for ten percent, Home and End for the ends, and Delete to remove it.

Stop at 0%
0px
8px
0deg
1
0
Animation
320ms
0ms
Live preview

The preview runs in a fully sandboxed frame: it loads no script and makes no request. The frame is used so a generated @keyframes name cannot collide with this site's own.

CSS
@keyframes fade-in {
  0% {
    transform: translate(0px, 8px) rotate(0deg) scale(1);
    opacity: 0;
  }
  100% {
    transform: translate(0px, 0px) rotate(0deg) scale(1);
    opacity: 1;
  }
}

/* Only animate when the visitor has not asked for less motion. Written as
   no-preference rather than as a reduce override, so a browser that does
   not understand the query never starts the animation at all. */
@media (prefers-reduced-motion: no-preference) {
  .fade-in {
    animation: fade-in 320ms ease-out 0ms 1 normal both;
  }
}

Related tools

What it does

Builds a CSS @keyframes animation on a timeline. Each stop sits at a percentage and carries a translate, a rotation, a scale, an opacity, and an optional background color, all edited with sliders while a preview element runs the result. It writes the @keyframes block, the animation shorthand with every longhand spelled out, and, if you want it, the prefers-reduced-motion guard that keeps the animation from running for visitors who asked for less of it.

How to use it

Pick a preset, then add stops on the timeline and adjust each one. Set the duration, the timing function, the iteration count, the direction, and the fill mode in the settings, and give the animation a name if you do not want the preset's. Press Replay to watch it again, then copy the CSS. The whole timeline lives in the URL, so a link reproduces exactly the animation you built.

Why this one

Most keyframe generators give you a fixed list of canned animations and no way to move a stop. This one is a real timeline: add a stop anywhere, drag it, and see the transform recomposed as the same function list at every stop, which is what a browser needs in order to interpolate at all. It also refuses an animation name that is a CSS-wide keyword, which is a silent failure that costs people an afternoon, and it offers the reduced motion guard by default. It runs entirely in the page, so your files and inputs never leave your device.

FAQ
Why does my transform animation jump instead of interpolating?
A browser can only interpolate two transforms whose function lists match in kind and in order. If one stop says translateY(20px) and the next says scale(1.1), there is nothing to interpolate between, so the value snaps at the stop boundary. This tool writes translate, rotate, and scale on every stop as soon as any stop needs a transform, which is exactly what keeps the animation smooth.
What does animation-fill-mode actually change?
It decides what the element looks like outside the animation's own running time. With the default of none, the element snaps back to its normal styles the moment the animation ends, which is why a fade in often flashes back to invisible. forwards holds the last keyframe, backwards applies the first keyframe during the delay, and both does the two together. For an entrance animation, both is almost always what you meant.
How should I handle prefers-reduced-motion?
Put the animation inside a prefers-reduced-motion: no-preference query rather than turning it off inside a reduce query. Written that way, a browser that does not understand the query never starts the animation at all, so the safe path is the default rather than the exception. Motion that conveys meaning, such as a progress spinner, can stay, but large movement, parallax, and anything that loops should not run for someone who asked for less of it.

Keyboard shortcuts: press ? anywhere on this page to see them.