ScreenshotNeo

BlogHow-to

CSS Animations: A Practical Tutorial

Learn CSS keyframes, timing, repetition, and reduced-motion support with a runnable example and practical troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

CSS keyframe animations let you describe an element’s visual states in CSS and tell the browser how to move between them. Define the sequence with @keyframes, apply it with an animation property, and use prefers-reduced-motion to remove or reduce nonessential movement for people who request it.

Here is a complete example you can save as index.html and open in a browser:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>CSS animation demo</title>
  <style>
    * { box-sizing: border-box; }
    body {
      min-height: 100vh;
      margin: 0;
      display: grid;
      place-items: center;
      font: 1rem/1.5 system-ui, sans-serif;
      background: #f4f6fb;
      color: #172033;
    }
    .card {
      width: min(22rem, calc(100% - 2rem));
      padding: 2rem;
      border-radius: 1rem;
      background: white;
      box-shadow: 0 1rem 3rem rgb(23 32 51 / 12%);
      animation-name: arrive;
      animation-duration: 700ms;
      animation-timing-function: ease-out;
      animation-delay: 100ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }
    @keyframes arrive {
      from { opacity: 0; transform: translateY(0.75rem); }
      to   { opacity: 1; transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .card {
        animation: none;
        opacity: 1;
        transform: none;
      }
    }
  </style>
</head>
<body>
  <main class="card">
    <h1>Welcome</h1>
    <p>This card fades in and rises into place.</p>
  </main>
</body>
</html>

The initial and final styles are the two keyframes. The animation settings determine when and how the browser plays that sequence. This separation—sequence in @keyframes, playback in animation properties—is the core pattern. MDN describes CSS animations as a way to animate transitions between CSS style configurations: Using CSS animations.

1. Define the keyframes

The name after @keyframes is an identifier that you reference with animation-name. In this example, the animation is named arrive.

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

from means the start of the animation (0%), and to means its end (100%). You can instead use percentages and add intermediate states:

@keyframes feedback {
  0%   { transform: scale(1); }
  60%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}

The browser interpolates between the declared states where the property supports interpolation. If a property is absent at a keyframe, the browser may interpolate it from the underlying style; for predictable results, declare the important beginning and ending values. A keyframe animation changes appearance over time; it does not add content or change the element’s semantic meaning.

2. Configure duration and easing

Duration controls the time for one iteration. Timing function controls the pace within that time. These longhand declarations make the roles explicit:

.card {
  animation-name: arrive;
  animation-duration: 700ms;
  animation-timing-function: ease-out;
}

Common timing functions include:

  • linear: constant rate from start to finish.
  • ease: starts and ends more gradually; this is the default.
  • ease-in: starts gradually and accelerates.
  • ease-out: starts quickly and slows toward the end.
  • ease-in-out: eases at both ends.
  • cubic-bezier(x1, y1, x2, y2): define a custom curve.
  • steps(count, end) or steps(count, start): jump through discrete states rather than interpolate smoothly; useful for frame-like effects.

For a perpetual rotation or progress indicator, a linear curve often looks consistent. For a short entrance, easing can make the movement settle naturally. Choose duration and easing based on the purpose of the effect; there is no universal correct duration.

If duration is omitted, it defaults to 0s, so there is no visible animation over time. A delay does not substitute for a duration.

3. Set delay, repetition, direction, and fill

After the basic name, duration, and easing, these controls determine when the sequence starts, how many times it runs, and which styles apply outside its active interval.

Property What it controls Example
animation-delay Wait before the first iteration. A negative delay starts partway through the sequence. animation-delay: 200ms;
animation-iteration-count Number of iterations, or infinite. animation-iteration-count: 2;
animation-direction normal plays forward; reverse plays backward; alternate switches direction each iteration; alternate-reverse starts backward and switches. animation-direction: alternate;
animation-fill-mode Whether animated styles apply before or after the active interval. animation-fill-mode: both;
animation-play-state Whether an animation is running or paused. animation-play-state: paused;

Fill mode is a common source of surprises. none leaves the element at its normal underlying style outside the active interval. forwards keeps the final animated state after completion; backwards applies the first relevant keyframe during a delay; both does both. In the example, both applies the starting state during the 100ms delay and retains the ending state after the one run.

Use infinite sparingly. Continuous decorative movement can distract, consume resources, or be uncomfortable. If a control pauses an animation, make the control’s state understandable and keyboard accessible.

4. Use the animation shorthand

Once the parts are familiar, the shorthand is compact:

.card {
  animation: arrive 700ms ease-out 100ms 1 normal both;
}

The order here is name, duration, timing function, delay, iteration count, direction, and fill mode. In the shorthand, the first time value is the duration and the second time value is the delay. For example, animation: arrive 700ms ease-out 100ms means a 700ms duration and a 100ms delay. Omitting the duration makes it 0s.

The shorthand also resets omitted animation sub-properties to their initial values. If you use newer animation features such as a timeline, check the shorthand’s reset behavior and declare the timeline in the order required by the property documentation. When debugging an ambiguous shorthand, temporarily expand it into longhand declarations.

You can define multiple comma-separated animations. Each comma-separated list in the related longhand properties corresponds by position; shorter lists repeat their values as needed. Keep names and ordering aligned, or use separate rules to make complex combinations easier to maintain.

5. Respect reduced-motion preferences

People can configure their operating system or device to reduce nonessential motion. CSS exposes that preference through prefers-reduced-motion. A reduced-motion rule should keep content and function available while removing or substantially reducing decorative movement.

@media (prefers-reduced-motion: reduce) {
  .card {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

Put the non-animated appearance in the base styles where possible, then add motion as an enhancement. This helps ensure content remains visible if animation does not run. For a sequence that communicates essential information, provide an equivalent non-moving way to understand that information instead of simply hiding it.

MDN documents the prefers-reduced-motion media feature. W3C’s C39 technique describes using it to prevent motion; W3C notes that techniques are examples of ways to meet criteria, not the only required implementation.

6. Choose CSS or script-driven animation

Approach Good fit Where the sequence and control live
CSS keyframes Self-contained decorative or interface effects with a known sequence. Keyframes and playback settings are declared in CSS.
Web Animations API or other script Cases that need programmatic control, such as coordinating playback with application state. JavaScript controls the animation; the Web Animations API can also interact with CSS animations.

CSS is convenient for declarative effects, and browsers can optimize some browser-controlled sequences. That does not mean CSS is always faster or better than JavaScript. Profile the actual page when performance matters. Whatever mechanism drives motion, provide a reduced-motion experience; JavaScript can inspect the same preference with matchMedia('(prefers-reduced-motion: reduce)'). See MDN’s overview of the Web Animations API for programmatic control.

7. Performance and reliability checklist

  • Animate only what needs to move. Avoid applying an infinite animation to many elements without a clear purpose.
  • Prefer simple effects. Transform and opacity are common choices for entrance and emphasis effects; complex effects can require more rendering work. Check the behavior in the browsers and devices you support.
  • Keep the resting state usable. Content should remain legible if animation is disabled, delayed, or unsupported.
  • Check interaction and focus. Motion should not obscure controls, move focus, or make a task harder to complete.
  • Test reduced motion. Enable the operating system preference or emulate it in browser developer tools, then confirm that content remains visible and the purpose of the interface is clear.
  • Do not promise a frame rate from CSS alone. Rendering cost depends on the effect, page, browser, and device. Inspect and profile the real page if an animation appears janky.

CSS itself does not add a per-use fee. The practical costs are implementation and maintenance time, testing across the browsers and devices you support, and any rendering work the effect adds at runtime. Keep effects purposeful and simple, then measure if users report performance problems.

8. Troubleshooting common problems

Symptom Likely cause Fix
Nothing moves. The animation name does not match a @keyframes rule, the rule is missing, or duration is omitted and therefore zero. Match the spelling and case of the name, ensure the keyframes are loaded, and set a nonzero duration.
The element appears to jump at the end. The underlying style differs from the final keyframe and fill mode does not retain the final frame. Make the underlying style match the intended resting state, or choose a suitable fill mode such as forwards.
The element flashes before starting. A delay is present but the initial animated state is not applied during it. Use backwards or both fill mode when appropriate, and ensure the initial state is readable.
The animation runs too quickly or starts late. Duration and delay may have been confused in the shorthand. Remember that the first time value is duration and the second is delay; use longhands while debugging.
A property does not interpolate smoothly. The property or values may not interpolate as expected, or keyframes may omit a necessary value. Check the property’s animation behavior, provide explicit compatible values at keyframes, and use a simpler supported effect if needed.
The animation repeats in the wrong direction. Direction and iteration count interact; a single iteration has no next pass to alternate into. Check both animation-direction and animation-iteration-count; use two or more iterations to see alternation.
The reduced-motion rule has no visible effect. The device preference may not be enabled, the selector may not match, or a later/more-specific rule may override it. Emulate the preference, inspect computed styles, and place a sufficiently specific override after the animated rule.
Motion persists after trying to disable the CSS animation. The motion may come from a transition, script, video, or another animated element. Identify the source and provide an equivalent reduced-motion path for that mechanism too.

9. Or skip the browser setup

If your task is capturing a page rather than building its animation, ScreenshotNeo is a website screenshot API and MCP server for developers. A GET request can return a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for its options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses report the page verdict and billing status in headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots. Yearly billing gives two months free, and every feature is available on every plan.

Sign up free for 1,000 screenshots a month, with no card required.

10. FAQ

Can I animate an element when a user hovers over it?

Yes. A hover state can change a class or property that starts an animation, or a CSS transition can interpolate the state change. Make sure the same information or action is available without hover, including on touch devices.

Can I animate a property that is not numeric?

Some properties switch discretely or have special interpolation rules rather than smoothly blending. Check the property’s animation behavior and use intermediate keyframes or a different visual technique where needed.

Do CSS animations replace JavaScript?

No. They cover many styling-driven sequences. Use script or the Web Animations API when the behavior needs programmatic coordination or control, and carry reduced-motion support through to that implementation.

Where can I learn more?

Start with MDN’s CSS animation guide, then explore the property references linked above. The book CSS Animations and Transitions for the Modern Web is an optional further-reading resource; the example is complete without it.