Toolmingo
Guides8 min read

CSS Animations Cheat Sheet: Transitions & @keyframes Reference

Complete CSS animations reference — transitions, @keyframes, all animation properties with examples, common patterns (fade, slide, spin, pulse), performance tips, and a quick-copy cheat sheet.

CSS has two independent systems for motion: transitions (smooth value changes triggered by state) and animations (@keyframes sequences that run automatically). Knowing when to use each — and all their properties — is what separates static UIs from polished ones.

This cheat sheet covers every property with working code, eight common patterns, performance rules, and the mistakes that cause jank.


Quick reference

Task Code
Fade on hover transition: opacity 0.3s ease
Slide down transition: transform 0.4s ease + translateY(-100%)→0
Spin forever animation: spin 1s linear infinite
Pulse once animation: pulse 0.6s ease-out
Delay start transition-delay: 0.2s
Fill mode animation-fill-mode: forwards
Pause/play animation-play-state: paused
Reduce motion @media (prefers-reduced-motion: reduce)

Part 1 — CSS Transitions

Transitions animate a property from its current value to a new value when that property changes (e.g., on :hover, class toggle, or JS .style change).

The shorthand

.element {
  transition: <property> <duration> <timing-function> <delay>;
}
/* Single property */
.btn {
  background: blue;
  transition: background 0.3s ease;
}
.btn:hover {
  background: darkblue;
}

/* Multiple properties */
.card {
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}

/* All properties (use sparingly — hits performance) */
.element {
  transition: all 0.3s ease;
}

Transition properties

Property Values Default
transition-property all, none, or CSS property names all
transition-duration 0.3s, 300ms 0s
transition-timing-function ease, linear, ease-in, ease-out, ease-in-out, cubic-bezier(), steps() ease
transition-delay 0s, 0.2s 0s

Timing functions

/* Built-in keywords */
transition-timing-function: ease;         /* slow → fast → slow (default) */
transition-timing-function: linear;       /* constant speed */
transition-timing-function: ease-in;      /* slow start */
transition-timing-function: ease-out;     /* slow end */
transition-timing-function: ease-in-out;  /* slow start + slow end */

/* Custom cubic-bezier — use cubic-bezier.com to design */
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring overshoot */

/* Steps — discrete jumps (typewriter, sprite animations) */
transition-timing-function: steps(4, end);

What you can and cannot transition

Animatable: opacity, transform, color, background-color, width, height, padding, margin, border-radius, box-shadow, font-size, top/left/right/bottom.

Not animatable: display, visibility (use opacity instead), font-family, content, background-image.


Part 2 — CSS Animations (@keyframes)

Animations let you define multi-step sequences that run automatically — no state change needed.

Basic syntax

/* 1. Define the keyframes */
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* 2. Apply to an element */
.modal {
  animation: fadeIn 0.4s ease forwards;
}

The shorthand

animation: <name> <duration> <timing-function> <delay>
           <iteration-count> <direction> <fill-mode> <play-state>;
.spinner {
  animation: spin 1s linear infinite;
}

.toast {
  animation: slideUp 0.3s ease-out 0.5s 1 normal forwards running;
  /* name duration timing delay count direction fill-mode play-state */
}

Animation properties

Property Values Purpose
animation-name keyframe name Which @keyframes block
animation-duration 0.5s, 1000ms How long one cycle takes
animation-timing-function same as transition Speed curve per cycle
animation-delay 0s, 0.2s, -0.5s (negative = start mid-animation) When to start
animation-iteration-count 1, 3, infinite How many times
animation-direction normal, reverse, alternate, alternate-reverse Forward, backward, or ping-pong
animation-fill-mode none, forwards, backwards, both What values apply outside active period
animation-play-state running, paused Control via JS

animation-fill-mode explained

@keyframes slideDown {
  from { transform: translateY(-20px); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}

/* none      — snaps back to original value after animation ends */
/* forwards  — stays at the "to" value when done (most common) */
/* backwards — applies "from" value during delay period */
/* both      — forwards + backwards */
.element {
  animation: slideDown 0.4s ease forwards;
}

Keyframe percentages

@keyframes bounce {
  0%   { transform: translateY(0);    }
  30%  { transform: translateY(-20px); }
  50%  { transform: translateY(0);    }
  70%  { transform: translateY(-10px); }
  100% { transform: translateY(0);    }
}

/* Multiple properties in one keyframe */
@keyframes popIn {
  0%   { transform: scale(0.8); opacity: 0; }
  60%  { transform: scale(1.05); }
  100% { transform: scale(1);   opacity: 1; }
}

Common patterns

1. Fade in

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.page-enter {
  animation: fadeIn 0.4s ease forwards;
}

2. Slide in from top

@keyframes slideDown {
  from { transform: translateY(-100%); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}

.notification {
  animation: slideDown 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

3. Infinite spin (loading spinner)

@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid #e2e8f0;
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: spin 0.75s linear infinite;
}

4. Pulse / heartbeat

@keyframes pulse {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%       { transform: scale(1.05); opacity: 0.8; }
}

.live-badge {
  animation: pulse 2s ease-in-out infinite;
}

5. Shake (error state)

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%       { transform: translateX(-6px); }
  40%       { transform: translateX(6px); }
  60%       { transform: translateX(-4px); }
  80%       { transform: translateX(4px); }
}

.input--error {
  animation: shake 0.4s ease;
}

6. Typewriter

@keyframes type {
  from { width: 0; }
  to   { width: 100%; }
}

.typewriter {
  overflow: hidden;
  white-space: nowrap;
  border-right: 2px solid;
  font-family: monospace;
  animation:
    type 2s steps(30, end),
    blink 0.75s step-end infinite;
  width: fit-content;
}

@keyframes blink {
  50% { border-color: transparent; }
}

7. Skeleton loading

@keyframes shimmer {
  0%   { background-position: -200% 0; }
  100% { background-position:  200% 0; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    #e2e8f0 25%,
    #f1f5f9 50%,
    #e2e8f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
  height: 1em;
}

8. Staggered list entrance

@keyframes fadeUp {
  from { transform: translateY(16px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

.list-item {
  animation: fadeUp 0.4s ease forwards;
  opacity: 0; /* start hidden */
}

/* Stagger via nth-child or CSS custom properties */
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 60ms; }
.list-item:nth-child(3) { animation-delay: 120ms; }
.list-item:nth-child(4) { animation-delay: 180ms; }

/* Or via JS: element.style.setProperty('--i', index) */
/* .list-item { animation-delay: calc(var(--i) * 60ms); } */

Performance — the golden rules

Only animate transform and opacity. These are composited by the GPU and don't trigger layout or paint.

/* GOOD — GPU composited, no layout recalculation */
.box { transition: transform 0.3s ease, opacity 0.3s ease; }

/* BAD — triggers layout (reflow) every frame */
.box { transition: width 0.3s ease, top 0.3s ease; }

Properties and their cost:

Property Triggers Cost
transform Composite Very low ✓
opacity Composite Very low ✓
color, background-color Paint Medium
width, height, top, left Layout + Paint High ✗
box-shadow Paint Medium

will-change — use sparingly

/* Promotes element to its own layer before animation starts */
.animated-card {
  will-change: transform;
}

/* Remove it after animation ends (saves memory) */
element.addEventListener('animationend', () => {
  element.style.willChange = 'auto';
});

Respecting reduced motion

Always wrap decorative animations in a media query:

@keyframes slideIn {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}

.sidebar {
  animation: slideIn 0.4s ease forwards;
}

/* Users who have "Reduce Motion" enabled in their OS settings */
@media (prefers-reduced-motion: reduce) {
  .sidebar {
    animation: none;
    /* Or provide an instant/minimal alternative */
  }
}

/* Or as a blanket rule */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Controlling animations with JavaScript

const el = document.querySelector('.spinner');

// Pause / resume
el.style.animationPlayState = 'paused';
el.style.animationPlayState = 'running';

// Restart animation (trick: remove, force reflow, add back)
el.classList.remove('animate');
void el.offsetWidth;  // triggers reflow
el.classList.add('animate');

// Listen for completion
el.addEventListener('animationend', (e) => {
  console.log(`${e.animationName} finished`);
});

// Stagger using custom properties
document.querySelectorAll('.item').forEach((item, i) => {
  item.style.setProperty('--i', i);
});
// CSS: animation-delay: calc(var(--i) * 80ms);

6 common mistakes

1. Animating layout-triggering properties width, height, top, left, margin trigger layout recalculation every frame. Use transform: scaleX() / translate() instead.

2. Forgetting animation-fill-mode: forwards Without it, the element snaps back to its original style when the animation ends. Almost always set to forwards for entrance animations.

3. transition: all — too broad It animates every property that changes, including display (which can't be animated) and causes unnecessary repaints. Be explicit: transition: transform 0.3s ease, opacity 0.3s ease.

4. No prefers-reduced-motion support Vestibular disorders and epilepsy make motion dangerous for some users. Always add the @media (prefers-reduced-motion: reduce) guard on significant animations.

5. will-change on everything will-change: transform promotes to a GPU layer — useful right before animation, harmful if left on permanently (consumes GPU memory). Apply it just before animation starts, remove it after.

6. Restarting animation by toggling class twice without reflow If you remove and immediately re-add a class, the browser batches changes and the animation doesn't restart. Force a reflow with void el.offsetWidth between the remove and add.


6 FAQ

What's the difference between transitions and animations? Transitions react to a state change (hover, class toggle) and go from A to B. Animations run independently via @keyframes, can loop, go through multiple steps, and start without a trigger.

Can I animate SVG properties? Yes. fill, stroke, stroke-dashoffset (for draw-on effects), opacity, and transform all work. SVG animations are widely used for icon effects and path drawing.

How do I chain animations? Use animation-delay equal to the previous animation's duration, or listen to animationend and add the next class via JS. For complex sequences, the Web Animations API (element.animate()) gives programmatic control.

Why does my animation flicker in Chrome? Usually a compositing layer issue. Add transform: translateZ(0) or will-change: transform to force GPU compositing. Also check for half-pixel values in translate.

Can I use CSS animations with React? Yes. Apply animation classes conditionally, use AnimatePresence from Framer Motion for unmount animations, or drive CSS animations with useEffect. The animation-play-state trick works well with useState.

What's cubic-bezier and where do I get values? It's a mathematical curve defining acceleration. Use the Chrome DevTools cubic-bezier editor by clicking the curve icon next to transition in the Styles panel, or search "cubic bezier editor" online. Common presets: (0.25, 0.46, 0.45, 0.94) = ease-out soft, (0.34, 1.56, 0.64, 1) = spring overshoot.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools