CSS Animation Generator

Build a keyframe animation from thirteen presets, replay it as you tune it, and copy CSS that only animates transform and opacity and respects reduced motion.

Enable JavaScript to play the animation; the CSS below is for the defaults.

Entrances fade or move in; pulse, bounce, shake and spin repeat.

200 to 500ms for an entrance. Past about 700ms an interface feels slow.

ease-out for anything entering: fast to start, settling at the end. linear only for a spin.

How far a slide, bounce or shake travels. Written as a custom property so one value drives every frame.

Direction

alternate plays forwards then backwards, which is what makes a pulse read as breathing.

Fill mode

both holds the first frame before it starts and the last one after: without it an entrance flashes.

Left empty it comes from the preset. Set it when the page has several animations.

Watch me

@keyframes slide-up-1 { 0% { opacity: 0; transform: translateY(var(--distance, 24px)); } 100% { opacity: 1; transform: translateY(0); } }

animation.css
@keyframes slide-up {
	0% {
		opacity: 0;
		transform: translateY(var(--distance, 24px));
	}

	100% {
		opacity: 1;
		transform: translateY(0);
	}
}

.animated {
	--distance: 24px;
	animation: 400ms cubic-bezier(0.22, 1, 0.36, 1) both slide-up;
}

@media (prefers-reduced-motion: reduce) {
	.animated {
		animation-duration: 1ms;
		animation-iteration-count: 1;
	}
}

Build a keyframe animation, watch it replay as you tune it, and copy CSS that animates the two properties a browser can animate cheaply.

How to use

  1. Pick the animation. The first eight are entrances and exits; pulse, bounce, shake, spin and flip are for attention or for loading.
  2. Set the duration. Between 200 and 500 milliseconds for an entrance; past about 700 an interface starts to feel slow.
  3. Choose the easing. ease-out for anything arriving, because it starts fast and settles; linear only for a spin, where a constant speed is the point.
  4. Set the distance. It is written as a custom property, so one value drives every frame of a slide, a bounce or a shake.
  5. Copy the CSS. It includes the reduced motion query; leave that in.

Example

@keyframes slide-up {
	0% {
		opacity: 0;
		transform: translateY(var(--distance, 24px));
	}

	100% {
		opacity: 1;
		transform: translateY(0);
	}
}

.animated {
	--distance: 24px;
	animation: 400ms cubic-bezier(0.22, 1, 0.36, 1) both slide-up;
}

@media (prefers-reduced-motion: reduce) {
	.animated {
		animation-duration: 1ms;
		animation-iteration-count: 1;
	}
}

The guard shortens the animation rather than removing it. animation: none on an entrance that starts at opacity: 0 would leave the element invisible for good, which is the most common way this setting gets broken.

Pitfalls

Compatibility

Keyframe animations, the animation shorthand and cubic-bezier() easings are supported unprefixed by every browser in use. prefers-reduced-motion needs Chrome 74, Firefox 63 or Safari 10.1. Custom properties inside keyframes work everywhere, though a custom property cannot itself be animated without @property. The tool runs entirely in your browser.

Frequently asked questions

Which properties are safe to animate?
transform and opacity. filter is usually fine too. Everything else risks a layout on every frame.
How long should an animation be?
Short. 200 to 300 milliseconds for something small, 400 to 500 for something entering from a distance.
Why does my animation flash before it starts?
No fill-mode. both holds the first frame until it begins.
What is the difference between an animation and a transition?
A transition runs between two states when one changes. An animation runs a sequence on its own, can repeat, and can have as many steps as you like.
How do I trigger it on scroll?
Add the class with an IntersectionObserver, or use animation-timeline: view() where support allows. Do not rely on it playing on load.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.