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
| Property | Values | Description | Default |
|---|---|---|---|
| animation-name | bounce | References @keyframes name | none |
| animation-duration | 1s | One cycle length | 0s |
| animation-iteration-count | infinite | 3 | How many times to run | 1 |
| animation-direction | alternate | Forward, reverse, or both | normal |
| animation-fill-mode | forwards | both | Keep final state after ending | none |
| animation-play-state | paused | running | Pause / resume | running |
| animation-delay | 0.2s | Wait before starting | 0s |
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
- Entrance effects β Fade-in, slide-up, or scale-up elements as they appear on the page.
- Loading indicators β Skeleton screens, spinners, progress animations.
- Attention grabbers β Pulsing badges, bouncing CTAs, notification dots.
- Continuous decoration β Floating elements, gradient shifts, parallax backgrounds.
- Use transitions instead when: You only need A β B on hover/focus β transitions are simpler and more performant for two-state changes.
β οΈ Common Mistakes
- Missing animation-fill-mode β Without forwards or both, the element snaps back to its original state after the animation ends. Use both to apply the first keyframe during delay and keep the last keyframe after completion.
- Animating layout properties β Animating width, height, or top triggers reflow every frame. Use transform and opacity for 60fps performance.
- Too many simultaneous animations β More than 3-4 animated elements on screen can cause jank on mobile devices. Be selective.
- Ignoring prefers-reduced-motion β Always include a reduced-motion media query. Users with vestibular disorders can experience nausea from animations.
- Forgetting animation-delay on staggered items β Without animation-fill-mode: both, delayed elements are visible in their pre-animation state before the delay completes.
- Using animation where transition suffices β Simple hover effects don't need @keyframes. Use transition for two-state changes β it's simpler and more maintainable.
π Lesson Complete
- β @keyframes defines multi-step animation sequences at specific percentages
- β animation-fill-mode: both preserves the first and final states
- β Stagger entrances with animation-delay on nth-child selectors
- β Prefer transform and opacity for smooth GPU-accelerated performance
- β animation-play-state: paused lets users control continuous animations
- β steps() timing creates typewriter and frame-by-frame effects
- β Skeleton loaders use gradient shimmer β a professional loading pattern
- β Always include @media (prefers-reduced-motion: reduce) for accessibility
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
- Previous: CSS Transitions & State Animations
- Next: Scroll-based Animations (scroll-behavior, scroll-timeline) β Animate elements as the user scrolls using CSS scroll-timeline
- Quick reference: HTML & CSS cheat sheet