A CSS animation generator that treats timing, easing and keyframes as one problem
Part of CSS LabKit, built by GrowQuest, leaders in web experiences.
Most tools in this space pick a single piece of the animation puzzle and stop there. You get a keyframes generator that outputs a fade or a slide with two flat states, or you get an easing curve tool that shows a pretty S-curve with nothing attached to it — no element moving, no timeline, no way to see what that curve actually does to a real multi-stop animation. Neither tells you what your animation looks like at the 340ms mark of a 600ms run, because neither has a timeline to ask.
MotionKit is built as one connected system: pick a preset with real multi-stop @keyframes, scale its motion up or down, shape the curve between each stop with a fully draggable cubic-bezier editor, then scrub the timeline to see any point in the animation on demand. Change the easing and the scrub position updates live, so cause and effect stay visible instead of requiring a mental model. The output is plain CSS — a @keyframes block and an animation shorthand that work with or without a framework.
Timing, easing and keyframes as one system
Drag the bezier curve and scrub the timeline in the same view, so cause and effect stay visible instead of requiring a mental model.
Real multi-stop presets
24 presets built from actual intermediate keyframes, not a single from/to pair scaled by a slider.
Framework-agnostic output
Plain @keyframes and an animation shorthand — nothing tied to a JS animation library or a specific build tool.
What you can do with MotionKit
What the browser is actually doing during a CSS animation
Every control maps to a real mechanism in the rendering engine. Understanding it explains the common failure modes — the animation that jumps back at the end, the one that stutters, the easing curve that looks wrong.
What @keyframes actually declares
A @keyframes rule is a named list of property states tied to percentages of a single animation cycle. The browser interpolates every animatable property between consecutive stops for you — you're describing checkpoints, not motion frame by frame. That's why a three-stop keyframe can produce an overshoot-and-settle motion a plain two-stop transition structurally cannot.
What the four cubic-bezier numbers mean
cubic-bezier(x1, y1, x2, y2) defines a curve from a fixed start (0,0) to a fixed end (1,1), with two control points bending it. The x-values are clamped to 0–1 because they represent time, but the y-values — progress — can go negative or past 1, which is exactly how you get anticipation or overshoot. Keyword values like ease-out are just named shorthand for specific control-point coordinates.
Why transform/opacity are cheap and top/left/width aren't
Animating transform and opacity can run entirely on the compositor thread — the browser hands the element off as its own layer and repositions or fades it each frame with no layout or paint. Animating top, left, width or margin forces layout recalculation on every frame, the layout-thrashing that makes an animation feel janky on lower-end hardware.
animation-fill-mode: what happens outside the active interval
By default an element snaps back to its pre-animation styles the instant the animation ends. forwards keeps it at its last keyframe's styles after completion; backwards applies the first keyframe's styles during any delay instead of the element's normal CSS. Skipping forwards is the usual cause of “it plays but then jumps back.”
animation vs. transition
A transition interpolates a property between two states in response to a trigger and needs that trigger to exist — a hover, a class toggle. An animation is self-contained: it runs from its own @keyframes, doesn't need an external state change to start, can have any number of stops, and can loop with animation-iteration-count: infinite.
prefers-reduced-motion, and how negative animation-delay seeks
prefers-reduced-motion: reduce reflects an OS-level accessibility setting — large or fast motion genuinely triggers vestibular discomfort for some users, so respecting it matters. Separately, animation-delay accepts negative values: -2000ms on a 5-second animation begins already two seconds in. Pausing the animation and setting a negative delay proportional to a scrub position is exactly how the timeline scrubber works.
Anatomy of the CSS behind a keyframe animation
A realistic multi-stop entrance animation, close to what a preset in the tool produces:
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translateY(24px) scale(0.96);
}
60% {
opacity: 1;
transform: translateY(-4px) scale(1.01);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.card {
animation: fade-in-up 600ms cubic-bezier(0.22, 1, 0.36, 1) 100ms 1 normal forwards;
}fade-in-upThe keyframe name, linking the @keyframes block to the animation shorthand — must match exactly.0%, 60%, 100%Checkpoints in the cycle; the browser interpolates every property between them. The 60% stop is what produces the overshoot before settling.600msTotal duration for one full cycle, start stop to end stop.cubic-bezier(0.22, 1, 0.36, 1)Shapes the rate of change across the cycle — this one is a fast-out, gentle-settle curve.100msDelay before the animation starts; during this window the element shows its 0% styles only if fill-mode includes backwards.1 normal forwardsIteration count, playback direction, and fill-mode — forwards keeps the element at its 100% styles after the animation ends instead of reverting.Best practices & design principles
Match duration to the size of the change
Micro-interactions — a button press, a toggle — read best around 150–250ms. Page-level transitions or larger reveals can stretch to 300–500ms without feeling sluggish, since the eye has more visual distance to track.
Let easing communicate direction, not just smooth things out
Entrances generally read better with ease-out (fast start, gentle arrival), mimicking something arriving under its own momentum. Exits read better with ease-in (gentle start, fast departure). Using the same curve for both is a common reason motion feels flat.
Animate one or two properties at a time
Stacking simultaneous changes to opacity, transform, colour and shadow on the same element reads as noisy rather than rich. Pick the property or two that actually carry the meaning of the transition and let the rest stay static.
Default to transform and opacity
They're compositor-friendly and won't trigger layout — worth treating as the default and everything else as a special case, especially on lower-powered devices or when several elements animate at once.
Always account for prefers-reduced-motion
At minimum, drop large translates and scales toward zero or swap to a plain opacity fade when the OS setting is enabled. It's a small conditional and the right default, not an edge case to skip.
Keep a small, consistent set of easing curves across a UI
Reusing the same two or three timing functions for entrances, exits and hover states makes an interface's motion feel like one coherent system rather than a pile of individually-tuned effects.
Why MotionKit exists
CSS animation ends up split across too many single-purpose tools: one site for browsing keyframe presets, a different one for dragging a bezier curve, a third for a timing-function cheat sheet — and none of them show what an easing curve actually does to a multi-stop animation over time, because none of them have both attached to the same timeline.
MotionKit puts the pieces where they belong: pick real motion, shape its curve, scrub through it, and see the exact CSS that produces what's on screen. It's built to hold up whether you're picking your first easing value or tuning the twentieth micro-interaction of a design system, and the output stays something you'd actually want to commit — plain @keyframes and an animation shorthand, not a wrapper around a runtime dependency.
If you're new to this
Start from a preset, watch it play, then drag the timeline to see exactly what's happening at any point in the cycle — you'll understand cubic-bezier() faster by dragging the curve than by reading about it.
If you do this daily
Skip straight to motion-amount and timing controls on a preset that's close, tune the curve by eye against the scrubbed timeline, and copy CSS that's already close to commit-ready.
Honest output
Every toggle for comments, CSS variables and minification defaults to off. What you copy is the CSS you saw animate, nothing extra bolted on unless you ask for it.
A better alternative to Animista, cubic-bezier.com and Ceaser
Animista, cubic-bezier.com and Ceaser are all genuinely useful and well known for good reason — but each solves a narrower slice of the problem than a full animation build usually needs.
Frequently asked questions about CSS animation
What's the difference between a CSS animation and a CSS transition?
A transition interpolates a property between two states in response to a trigger — a hover, a class change — and stops existing once that interpolation finishes. An animation is defined independently via @keyframes, can have any number of intermediate stops, doesn't need a trigger to start, and can loop with animation-iteration-count: infinite.
Which CSS properties are safe to animate for performance?
transform and opacity are the two that can run on the compositor thread without triggering layout or paint, which is why they're the standard recommendation for smooth animation. Properties like top, left, width, height and margin force layout recalculation on every frame and are worth avoiding for anything performance-sensitive.
What do the four numbers in cubic-bezier() mean?
They're two control points, (x1, y1) and (x2, y2), bending a curve running from a fixed (0,0) to a fixed (1,1). The x-values (time) are clamped between 0 and 1; the y-values (progress) can go outside that range, which is how you get overshoot or anticipation effects.
Why does prefers-reduced-motion matter for CSS animations?
It reflects an OS-level accessibility setting for users who experience real discomfort — nausea, vestibular symptoms — from large or fast motion, so respecting it is an accessibility requirement, not a nice-to-have. A common pattern is dropping translate/scale magnitude toward zero or falling back to a plain fade when the setting is active.
How do I make my CSS animation start partway through, like a scrubber?
Set animation-delay to a negative value. A negative delay tells the browser to act as if the animation had already been running for that long, so -2000ms on a 5-second animation starts already two seconds in — pairing this with animation-play-state: paused and updating the delay on drag is how a scrubbable timeline is built.
What's the difference between animation-fill-mode: forwards and backwards?
forwards keeps the element at its final keyframe's computed styles after the animation ends, instead of reverting to its pre-animation CSS. backwards applies the first keyframe's styles during any animation-delay, instead of the element's normal styles showing during that wait.
Can I use generated CSS animations without a JavaScript library?
Yes — @keyframes and the animation property are plain CSS, supported natively without any runtime dependency. A JS trigger snippet is only needed if you want to start or restart the animation in response to a script-driven event rather than a CSS state like :hover or a class toggle.
How long should a UI animation last?
It depends on scale: small feedback like a button press or toggle typically reads best around 150–250ms, while a larger transition — a panel opening, a page-level change — can extend to 300–500ms. Longer than that tends to feel like it's slowing the user down.