CSS Keyframe Animations & Motion Design

Reviewed & published by Brayan K

CSS keyframe animations use an @keyframes rule to define styles at multiple points along a timeline, letting an element loop through multi-step motion automatically without any JavaScript.

Part of the free HTML & CSS course at LearnCodingFast β€” hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

πŸ’‘ Think of It Like This

If CSS transitions are like a dimmer switch (smooth A β†’ B), keyframe animations are like a choreographed dance routine β€” you define every step: "at 0% do this, at 50% do that, at 100% finish here." You control the entire performance, not just the start and end.

Understanding Keyframe Animations

Transitions only animate between two states (A β†’ B). Keyframe animations let you define an unlimited number of intermediate states at specific percentages (0%, 25%, 50%, 75%, 100%). This makes complex, multi-step sequences possible β€” things like bouncing, morphing, color cycling, and loading indicators.

A keyframe animation has two parts: the @keyframes rule (which defines the steps) and the animation property (which applies it to an element). Unlike transitions, animations can run automatically on page load β€” no hover or click required. They can also loop infinitely, alternate direction, and be paused/resumed.

Performance remains critical: The same GPU-optimized properties apply β€” animate transform and opacity for butter-smooth 60fps. Animating width, height, or top causes layout thrashing and janky performance, especially on mobile.

Animation Properties Reference

PropertyValuesDescriptionDefault
animation-namebounceReferences @keyframes namenone
animation-duration1sOne cycle length0s
animation-iteration-countinfinite | 3How many times to run1
animation-directionalternateForward, reverse, or bothnormal
animation-fill-modeforwards | bothKeep final state after endingnone
animation-play-statepaused | runningPause / resumerunning
animation-delay0.2sWait before starting0s

Worked Example: The animation Shorthand, Unpacked

Almost every animation you will read online is written as the one-line animation: shorthand, and the values are positional β€” get the order wrong and the browser silently ignores the whole declaration. The worked example below shows the same animation twice: once as the shorthand, once with all eight longhand properties written out. Both boxes move identically.

The order is: name, duration, timing-function, delay, iteration-count, direction, fill-mode, play-state. Only name and duration are really required; everything else falls back to a default. One quirk worth knowing: if you give two time values, the first is always the duration and the second is always the delay.

<!DOCTYPE html>
<html>
<head>
  <title>The animation shorthand, unpacked</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 24px; background: #0f172a; color: #e5e7eb; }
    .track { border: 2px dashed #475569; border-radius: 8px; padding: 8px; margin: 8px 0 24px; }
    .box { width: 60px; height: 60px; border-radius: 8px; background: #3b82f6; }
    h3 { font-size: 14px; margin: 0 0 4px; color: #22c55e; }
    code { background: #1e293b; padding: 2px 5px; border-radius: 4px; font-size: 13px; }

    /* The timeline. Two stops is all this animation needs. */
    @keyframes slide {
      from { transform: translateX(0); }      /* 0%   - start here */
      to   { transform: translateX(120px); }  /* 100% - end here   */
    }

    /* THE SHORTHAND. The order is fixed and it is worth memorising:
       name  duration  timing-function  delay  iteration-count  direction  fill-mode  play-state */
    .short {
      animation: slide 2s ease-in-out 0.5s infinite alternate both running;
    }

    /* EXACTLY THE SAME THING, written out one property per line.
       Both boxes below move identically - the shorthand is only shorter. */
    .long {
      animation-name: slide;              /* which @keyframes rule to run */
      animation-duration: 2s;             /* how long ONE pass takes */
      animation-timing-function: ease-in-out;  /* slow, fast, slow */
      animation-delay: 0.5s;              /* wait before the first pass */
      animation-iteration-count: infinite; /* how many passes; default is 1 */
      animation-direction: alternate;     /* pass 2 runs backwards, then 3 forwards... */
      animation-fill-mode: both;          /* obey the "from" state during the delay too */
      animation-play-state: running;      /* set to "paused" to freeze it */
    }
  </style>
</head>
<body>
  <h1>One animation, two ways to write it</h1>

  <h3>Shorthand</h3>
  <code>animation: slide 2s ease-in-out 0.5s infinite alternate both running;</code>
  <div class="track"><div class="box short"></div></div>

  <h3>Longhand (identical result)</h3>
  <code>animation-name, -duration, -timing-function, -delay, -iteration-count, -direction, -fill-mode, -play-state</code>
  <div class="track"><div class="box long"></div></div>
  <!-- βœ… Expected result, measured in a real browser:
     .track -> padding-top: 8px
     .box -> width: 60px
     .track -> count: 2
  -->
</body>
</html>

Reading the computed styles of both boxes gives byte-for-byte identical values across all eight animation-* properties β€” slide, 2s, ease-in-out, 0.5s, infinite, alternate, both, running. The shorthand is not a different feature; it is the same eight properties in one line.

@keyframes Basics β€” Bounce, Pulse, Spin, Fade

The four most common animation patterns are bounce (vertical movement), pulse (scale in/out), spin (continuous rotation), and fade-in (opacity + position). Each uses @keyframes with different percentage stops. The fade card uses animation-fill-mode: both to stay visible after the animation ends.

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background: #fafafa; }
        @keyframes bounce {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-30px); }
        }
        @keyframes fadeInUp {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        @keyframes pulse {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.1); }
        }
        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }
        .demos { display: flex; gap: 32px; flex-wrap: wrap; margin: 24px 0; }
        .demo-item { text-align: center; }
        .demo-item p { font-size: 0.85rem; color: #666; margin-top: 8px; }
        .ball { width: 50px; height: 50px; border-radius: 50%; margin: 0 auto; }
        .bounce-ball { background: #F44336; animation: bounce 1s ease infinite; }
        .pulse-ball  { background: #4CAF50; animation: pulse 1.5s ease-in-out infinite; }
        .spin-box {
            width: 50px; height: 50px; background: #2196F3;
            border-radius: 8px; margin: 0 auto;
            animation: spin 2s linear infinite;
        }
        .fade-card {
            background: white; padding: 20px; border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            max-width: 300px;
            animation: fadeInUp 0.6s ease-out both;
        }
        .fade-card h3 { margin-top: 0; color: #1976D2; }
        h2 { color: #1565C0; }
    </style>
</head>
<body>
    <h1>@keyframes Basics</h1>
    <h2>Infinite Animations</h2>
    <div class="demos">
        <div class="demo-item">
            <div class="ball bounce-ball"></div>
            <p>bounce</p>
        </div>
        <div class="demo-item">
            <div class="ball pulse-ball"></div>
            <p>pulse</p>
        </div>
        <div class="demo-item">
            <div class="spin-box"></div>
            <p>spin</p>
        </div>
    </div>
    <h2>Entrance Animation</h2>
    <div class="fade-card">
        <h3>Welcome!</h3>
        <p>This card fades in and slides up on load using <code>fadeInUp</code>.</p>
    </div>
  <!-- βœ… Expected result, measured in a real browser:
     .demos -> display: flex
     .demo-item -> count: 3
  -->
</body>
</html>

Animation Properties β€” Staggering, Triggers, Pausing

animation-delay combined with :nth-child() creates staggered entrances where items slide in one after another. You can also trigger animations on hover (useful for attention effects) and pause infinite animations using animation-play-state: paused β€” perfect for tickers and carousels.

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: system-ui, sans-serif; padding: 24px; }
        @keyframes slideIn {
            from { transform: translateX(-100%); opacity: 0; }
            to   { transform: translateX(0); opacity: 1; }
        }
        .card-list { max-width: 400px; }
        .card {
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 16px;
            margin: 8px 0;
            animation: slideIn 0.5s ease-out both;
        }
        .card:nth-child(1) { animation-delay: 0s; }
        .card:nth-child(2) { animation-delay: 0.1s; }
        .card:nth-child(3) { animation-delay: 0.2s; }
        .card:nth-child(4) { animation-delay: 0.3s; }
        .card h4 { margin: 0 0 4px; color: #1976D2; }
        .card p  { margin: 0; color: #666; font-size: 0.9rem; }
        @keyframes highlight {
            0%   { background: #FFF3E0; }
            50%  { background: #FFE0B2; transform: scale(1.02); }
            100% { background: #FFF3E0; }
        }
        .highlight-box {
            padding: 16px;
            border-radius: 8px;
            margin: 24px 0;
            background: #FFF3E0;
            cursor: pointer;
        }
        .highlight-box:hover { animation: highlight 0.8s ease; }
        @keyframes scroll {
            from { transform: translateX(0); }
            to   { transform: translateX(-50%); }
        }
        .ticker-wrap {
            overflow: hidden;
            background: #1976D2;
            color: white;
            border-radius: 8px;
            padding: 12px 0;
            margin: 24px 0;
        }
        .ticker {
            display: flex;
            gap: 40px;
            white-space: nowrap;
            animation: scroll 10s linear infinite;
            width: max-content;
        }
        .ticker-wrap:hover .ticker { animation-play-state: paused; }
        h2 { color: #1565C0; }
        code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; }
    </style>
</head>
<body>
    <h1>Animation Properties</h1>
    <h2>Staggered Entrance</h2>
    <div class="card-list">
        <div class="card"><h4>Item 1</h4><p>Slides in first</p></div>
        <div class="card"><h4>Item 2</h4><p>0.1s delay</p></div>
        <div class="card"><h4>Item 3</h4><p>0.2s delay</p></div>
        <div class="card"><h4>Item 4</h4><p>0.3s delay</p></div>
    </div>
    <h2>Hover to Trigger</h2>
    <div class="highlight-box">Hover me for a highlight animation</div>
    <h2>Ticker (pause on hover)</h2>
    <div class="ticker-wrap">
        <div class="ticker">
            <span>πŸ“’ Breaking: CSS animations are powerful!</span>
            <span>🎨 Design with motion β€” not just static styles</span>
            <span>⚑ Use animation-play-state to pause on hover</span>
            <span>πŸ“’ Breaking: CSS animations are powerful!</span>
            <span>🎨 Design with motion β€” not just static styles</span>
        </div>
    </div>
  <!-- βœ… Expected result, measured in a real browser:
     .highlight-box -> cursor: pointer
     .ticker-wrap -> overflow: hidden
     .card -> count: 4
  -->
</body>
</html>

Multi-Step & Creative Animations

Multi-step keyframes (0%, 25%, 50%, 75%, 100%) create complex sequences. The color morph cycles through five colors while changing border-radius. The skeleton loader uses a gradient shimmer β€” a production-quality loading indicator used by Facebook, YouTube, and most modern apps. The typing effect combines steps() timing with a blinking cursor.

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: system-ui, sans-serif; padding: 24px; }
        @keyframes colorShift {
            0%   { background: #F44336; border-radius: 0; }
            25%  { background: #FF9800; border-radius: 10px; }
            50%  { background: #4CAF50; border-radius: 50%; }
            75%  { background: #2196F3; border-radius: 10px; }
            100% { background: #9C27B0; border-radius: 0; }
        }
        .morph-box {
            width: 100px; height: 100px;
            animation: colorShift 4s ease-in-out infinite;
            margin: 24px auto;
        }
        @keyframes shimmer {
            0%   { background-position: -200px 0; }
            100% { background-position: calc(200px + 100%) 0; }
        }
        .skeleton { max-width: 400px; margin: 24px 0; }
        .skeleton-line {
            height: 16px;
            margin: 8px 0;
            border-radius: 4px;
            background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
            background-size: 200px 100%;
            animation: shimmer 1.5s infinite linear;
        }
        .skeleton-line:first-child { width: 60%; height: 24px; }
        .skeleton-line:nth-child(2) { width: 100%; }
        .skeleton-line:nth-child(3) { width: 85%; }
        .skeleton-line:last-child { width: 40%; }
        @keyframes typing {
            from { width: 0; }
            to   { width: 22ch; }
        }
        @keyframes blink {
            50% { border-color: transparent; }
        }
        .typing-text {
            font-family: monospace;
            font-size: 1.4rem;
            width: 22ch;
            white-space: nowrap;
            overflow: hidden;
            border-right: 3px solid #1976D2;
            animation: typing 3s steps(22) 1s both, blink 0.7s step-end infinite;
            margin: 24px 0;
        }
        h2 { color: #1565C0; }
    </style>
</head>
<body>
    <h1>Multi-Step & Creative Animations</h1>
    <h2>Colour Morph (5 keyframes)</h2>
    <div class="morph-box"></div>
    <h2>Loading Skeleton</h2>
    <div class="skeleton">
        <div class="skeleton-line"></div>
        <div class="skeleton-line"></div>
        <div class="skeleton-line"></div>
        <div class="skeleton-line"></div>
    </div>
    <h2>Typing Effect</h2>
    <div class="typing-text">Hello, CSS Animations!</div>
  <!-- βœ… Expected result, measured in a real browser:
     .typing-text -> overflow: hidden
     .skeleton-line -> count: 4
  -->
</body>
</html>

Accessibility β€” prefers-reduced-motion

Some users experience motion sickness, vertigo, or distraction from animations. The @media (prefers-reduced-motion: reduce) query lets you disable or simplify animations for users who've enabled "Reduce motion" in their OS settings. This isn't optional β€” it's a core accessibility requirement.

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: system-ui, sans-serif; padding: 24px; }

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

        @keyframes float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-10px); }
        }

        .animated-card {
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 12px;
            padding: 24px;
            max-width: 350px;
            margin: 16px 0;
            animation: slideUp 0.6s ease-out both;
        }
        .animated-card:nth-child(2) { animation-delay: 0.15s; }
        .animated-card:nth-child(3) { animation-delay: 0.3s; }

        .animated-card h3 { margin-top: 0; color: #1976D2; }
        .animated-card p { color: #666; }

        .floating-icon {
            font-size: 2rem;
            display: inline-block;
            animation: float 3s ease-in-out infinite;
            margin: 16px;
        }
        .floating-icon:nth-child(2) { animation-delay: 0.5s; }
        .floating-icon:nth-child(3) { animation-delay: 1s; }

        /* Reduced Motion Override */
        @media (prefers-reduced-motion: reduce) {
            .animated-card {
                animation: none;
                opacity: 1;
            }
            .floating-icon {
                animation: none;
            }
        }

        .info-box {
            background: #E8F5E9;
            padding: 16px;
            border-radius: 8px;
            border-left: 4px solid #4CAF50;
            margin: 24px 0;
        }
        .code-example {
            background: #263238;
            color: #EEFFFF;
            padding: 16px;
            border-radius: 8px;
            font-family: monospace;
            font-size: 0.85rem;
            overflow-x: auto;
            white-space: pre;
            margin: 16px 0;
        }

        h2 { color: #1565C0; }
    </style>
</head>
<body>
    <h1>Animations & Accessibility</h1>

    <h2>Entrance Animations</h2>
    <div class="animated-card"><h3>Card 1</h3><p>Slides up with a 0s delay.</p></div>
    <div class="animated-card"><h3>Card 2</h3><p>Slides up with a 0.15s delay.</p></div>
    <div class="animated-card"><h3>Card 3</h3><p>Slides up with a 0.3s delay.</p></div>

    <h2>Decorative Floating</h2>
    <div>
        <span class="floating-icon">🎨</span>
        <span class="floating-icon">✨</span>
        <span class="floating-icon">πŸš€</span>
    </div>

    <div class="info-box">
        <strong>β™Ώ prefers-reduced-motion</strong><br>
        Users who enable "Reduce motion" in their OS settings will see no animations. Decorative motion is removed; essential state changes can use instant transitions instead.
    </div>

    <div class="code-example">@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}</div>
  <!-- βœ… Expected result, measured in a real browser:
     .floating-icon -> display: inline-block
     .animated-card -> count: 3
  -->
</body>
</html>

🎯 Your Turn: A Staggered Entrance

Staggered entrances are the single most common real use of keyframes: a list where each row flies in just after the one above it. The list below is fully styled and the delays are wired up β€” three ___ gaps are left for you.

The trap in blank 2: most people reach for forwards here, and it does nothing. forwards only holds the last keyframe after the animation ends. Your problem is at the start: during its 0.3s delay the third row has no animation applied yet, so it sits there fully visible and then jumps backwards to begin. The keyword that applies the first keyframe during the delay is backwards, and both gives you backwards and forwards together.

<!DOCTYPE html>
<html>
<head>
  <title>Your Turn: Staggered Entrance</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 32px; background: #0f172a; color: #e5e7eb; }
    ul { list-style: none; margin: 0; padding: 0; max-width: 320px; }
    li {
      background: #1e293b;
      border-left: 4px solid #22c55e;
      border-radius: 6px;
      padding: 14px 16px;
      margin-bottom: 10px;
    }

    /* 🎯 YOUR TURN β€” replace each ___ below. Everything else already works. */

    @keyframes slide-in {
      from {
        /* πŸ‘‰ 1) The item should START completely see-through.
               opacity runs from 0 (invisible) to 1 (solid). */
        opacity: ___;
        transform: translateX(-40px);   /* and 40px to the left */
      }
      to {
        opacity: 1;
        transform: translateX(0);       /* settle into its real position */
      }
    }

    li {
      /* πŸ‘‰ 2) Run slide-in for 0.6s with ease-out, and use the fill-mode
             keyword that makes the item obey the "from" state DURING its
             delay as well. (It is not "forwards" - read the note below.)
             Order: name duration timing fill-mode */
      animation: ___;
    }

    li:nth-child(2) { animation-delay: 0.15s; }

    /* πŸ‘‰ 3) Make the third item start 0.3s in, so the three arrive as a wave. */
    li:nth-child(3) { animation-delay: ___; }

    /* βœ… Expected result:
       The three rows fly in from the left one after another and stay put.
       Measured in Chrome 100ms after load, while item 3 is still waiting
       out its delay:
         - with the right fill-mode: opacity 0, translateX(-40px)  (hidden)
         - with no fill-mode:        opacity 1, transform none     (visible,
           and it will visibly JUMP backwards before sliding in)
       One second later both settle at opacity 1 and transform none. */

    @media (prefers-reduced-motion: reduce) {
      li { animation: none; }   /* always give motion-sensitive users an exit */
    }
  </style>
</head>
<body>
  <h1>Inbox</h1>
  <ul>
    <li>Your order has shipped</li>
    <li>New comment on your post</li>
    <li>Weekly summary is ready</li>
  </ul>
</body>
</html>
opacity: 0;
animation: slide-in 0.6s ease-out both;
animation-delay: 0.3s;

Measured 100ms after the page loads β€” while the third row is still inside its delay β€” the correct version reports opacity: 0 and a -40px X translation, while the version with no fill-mode reports opacity: 1 and transform: none. That difference is the flash you are removing.

πŸ† Mini-Challenge: Shake a Wrong Password

No blanks and no answers this time β€” a brief and a comment outline. Build the horizontal shake that login forms use when a password is rejected, and stack a second animation on top of it. It is a good test of multi-stop keyframes, because a shake has to end exactly where it began or the input will be left visibly crooked.

<!DOCTYPE html>
<html>
<head>
  <title>Mini-Challenge: Shake on Error</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 32px; background: #0f172a; color: #e5e7eb; }
    form { max-width: 320px; }
    label { display: block; font-size: 13px; margin-bottom: 6px; }
    input {
      width: 100%;
      box-sizing: border-box;
      padding: 10px 12px;
      border-radius: 6px;
      border: 2px solid #ef4444;
      background: #1e293b;
      color: #e5e7eb;
      font-size: 14px;
    }
    .error { color: #f87171; font-size: 13px; margin-top: 6px; }

    /* 🎯 MINI-CHALLENGE β€” write the rules yourself. No answers below.

       Build the "wrong password" shake that login forms use.

       1. @keyframes shake  -> a five-stop timeline:
            0% and 100%  ->  transform: translateX(0)
            25%          ->  translateX(-8px)
            50%          ->  translateX(8px)
            75%          ->  translateX(-4px)      (the wobble dies down)

       2. .shake -> animation: shake 0.4s ease-in-out;
          Put class="shake" on the input to see it. It should run ONCE,
          not forever - leave the iteration-count off and the default is 1.

       3. Add a second animation to the same element, comma separated:
            animation: shake 0.4s ease-in-out, flash 0.4s ease-in-out;
          with @keyframes flash animating border-color from #ef4444 to
          #7f1d1d and back.

       4. @media (prefers-reduced-motion: reduce)
            .shake -> animation: none; and instead just leave the red border.

       βœ… Check yourself:
          - The input must come to rest EXACTLY where it started. If it ends
            up shifted sideways, your 100% stop is missing or is not 0.
          - It must shake once and stop. If it shakes forever, you added
            "infinite" somewhere.
          - Animate transform, never margin-left or left - see the
            performance note earlier in this lesson.
    */
  </style>
</head>
<body>
  <form>
    <label for="pw">Password</label>
    <input id="pw" type="password" value="wrongpassword">
    <p class="error">That password is not right.</p>
  </form>
  <!-- βœ… Expected result, measured in a real browser:
     label -> display: block
     input -> box-sizing: border-box
  -->
</body>
</html>

When to Use Keyframe Animations

⚠️ Common Mistakes

πŸŽ‰ Lesson Complete

Practice quiz

Which CSS rule defines the steps of a keyframe animation?

  • @animation
  • @keyframes
  • @motion
  • @steps

Answer: @keyframes. The @keyframes rule defines styles at points along the timeline; the animation property then applies it to an element.

Inside @keyframes, which keyword pair is equivalent to 0% and 100%?

  • start and stop
  • begin and end
  • from and to
  • open and close

Answer: from and to. from is 0% and to is 100%, useful for simple two-state animations.

Which property makes an animation repeat forever?

  • animation-repeat: forever
  • animation-iteration-count: infinite
  • animation-loop: true
  • animation-count: all

Answer: animation-iteration-count: infinite. animation-iteration-count: infinite loops the animation endlessly; its default is 1.

Which value of animation-fill-mode keeps the element’s final keyframe state after the animation ends?

  • none
  • backwards
  • forwards
  • reset

Answer: forwards. forwards retains the last keyframe; both also applies the first keyframe during any delay.

How do you create a staggered entrance where items animate one after another?

  • Different animation-duration per item
  • Different animation-delay per item (e.g. via :nth-child)
  • Different animation-name per item
  • Different z-index per item

Answer: Different animation-delay per item (e.g. via :nth-child). Giving each item a larger animation-delay (commonly with :nth-child) staggers when each one starts.

Which property pauses or resumes a running animation?

  • animation-state
  • animation-play-state
  • animation-pause
  • animation-control

Answer: animation-play-state. animation-play-state: paused freezes the animation; running resumes it β€” handy for tickers on hover.

For smooth 60fps animation, which properties are best to animate?

  • width and height
  • top and left
  • transform and opacity
  • margin and padding

Answer: transform and opacity. transform and opacity are GPU-accelerated and skip layout; animating width/height/top causes reflow and jank.

Which timing function creates a typewriter / frame-by-frame effect?

  • ease-in-out
  • linear
  • steps(n)
  • cubic-bezier(0,0,1,1)

Answer: steps(n). steps(n) jumps between n discrete frames rather than interpolating smoothly, ideal for typing effects.

Which media query should you honor to disable motion for users who request it?

  • @media (prefers-reduced-motion: reduce)
  • @media (no-animation)
  • @media (motion: off)
  • @media (accessibility: reduce)

Answer: @media (prefers-reduced-motion: reduce). @media (prefers-reduced-motion: reduce) lets you remove or simplify animations for users who enabled Reduce Motion.

In `animation: bounce 1s ease infinite`, what does the `1s` specify?

  • The delay before starting
  • The duration of one cycle
  • The number of iterations
  • The timing function

Answer: The duration of one cycle. In the animation shorthand the first time value is the duration (animation-duration) of one cycle.

Continue this course