Senior 5 min · March 05, 2026

CSS width Animation — Why It Breaks 16ms Budgets on Mobile

Animating width forces layout recalc every frame, dropping below 60fps on budget Android.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • CSS transitions animate between two states triggered by property changes.
  • CSS @keyframes define multi-step timelines with unlimited waypoints.
  • Only transform and opacity trigger compositor-only rendering — all else causes layout/paint.
  • Staggering with CSS variables + calc() creates cascading entrances without JavaScript.
  • animation-fill-mode: both is the default for entrance animations to avoid flash.
  • Always include @media (prefers-reduced-motion: reduce) for accessibility compliance.
Plain-English First

Imagine a light switch that teleports from OFF to ON — that's a style change with no transition. Now imagine a dimmer switch that smoothly fades the light up over two seconds — that's a CSS transition. A CSS animation is like a choreographed dance routine: you define exactly what the dancer does at each beat (0%, 50%, 100%), and the browser performs it on cue, even without anyone touching the switch.

Every polished web product you've ever used — Stripe's pricing page, Linear's task cards, Apple's product reveals — has one thing in common: motion that feels intentional. That motion isn't magic. It's CSS transitions and animations doing the heavy lifting without a single line of JavaScript. When an element moves, fades, or bounces in a way that feels natural, your brain registers the UI as trustworthy and responsive. Skip the motion and everything feels like a 1997 government website.

The problem most developers run into isn't knowing the syntax — it's knowing WHY the browser behaves the way it does, WHEN to reach for a transition versus a full animation, and HOW to avoid jank that tanks your Lighthouse scores. Animating the wrong CSS property (looking at you, margin) can silently murder your 60fps budget. Animating transform and opacity instead keeps things silky because those two properties skip the expensive layout and paint steps entirely.

By the end of this article you'll know exactly how the browser renders motion, when to use transition versus @keyframes, how to sequence multi-step animations, and the exact gotchas that trip up even experienced developers — so you can ship smooth, performant UI with confidence.

CSS Transitions — Smooth State Changes Without a Single Keyframe

A CSS transition is the simplest form of animation: you tell the browser 'when THIS property changes, don't snap — glide over X seconds.' That's it. No keyframes, no JavaScript, no complexity.

The four pillars of transition are: the property you're watching, the duration of the glide, the timing function (the pace curve), and the delay before it starts. You almost always set these on the element's default state — not the hover or active state — so the transition runs in both directions (in AND out).

The timing function is where most developers stop reading, but it's where all the personality lives. ease starts fast and slows down (great for elements entering the screen). ease-in-out ramps up then back down (great for elements moving across the screen). linear is robotic and usually wrong for UI — it's only really useful for infinite loaders. cubic-bezier() lets you craft a custom curve for that satisfying 'spring' feeling.

The golden rule: only transition transform and opacity for anything that must hit 60fps. Transitioning width, height, margin, or top forces the browser to recalculate layout on every frame — that's expensive and causes jank on lower-end devices.

ButtonTransition.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* ── Base button styles ──────────────────────────────────────── */
.cta-button {
  display: inline-block;
  padding: 14px 28px;
  background-color: #4f46e5;
  color: #ffffff;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;

  transition:
    transform 180ms ease-out,
    background-color 180ms ease-out,
    box-shadow 180ms ease-out;
}

.cta-button:hover {
  background-color: #4338ca;
  transform: translateY(-3px);
  box-shadow: 0 8px 20px rgba(79, 70, 229, 0.4);
}

.cta-button:active {
  transform: translateY(0px);
  box-shadow: none;
  transition-duration: 80ms;
}
Output
Visual result:
• At rest → flat indigo button, no shadow
• On hover → lifts 3px, darker background, soft purple glow (180ms ease-out)
• On click → snaps back to flat (80ms) giving a physical 'press' sensation
• On un-hover → glides back to rest state (transition plays in reverse automatically)
Pro Tip: The 100ms Rule
UI transitions that respond to direct interaction (hover, click) should live between 80ms–200ms. Any shorter feels broken; any longer feels sluggish. Save 300ms–600ms durations for page-level transitions like modals opening or panels sliding in — those need the extra time because they carry more visual weight.
Production Insight
The most common production bug is putting transition on :hover instead of the base state.
This breaks the return animation — the element snaps back instantly on mouse leave.
Rule: always declare transition on the element's default selector, never on the interactive pseudo-class.
Key Takeaway
Put transition on the base state.
The hover state only sets destination values.
Otherwise the out animation is instant — users notice.

CSS Keyframe Animations — When You Need a Full Choreography

Transitions only go from A to B. But what if you need A → B → C → back to A, looping forever? That's where @keyframes come in. A keyframe animation defines a named timeline with as many waypoints as you need, then you attach that timeline to any element via animation-name.

Think of @keyframes as writing a script for a play. transition is an actor who improvises between two lines. @keyframes is a full stage direction that says: at the opening curtain do THIS, at the halfway point do THAT, and at the curtain call do THIS OTHER THING.

The animation shorthand packs in eight sub-properties: name, duration, timing-function, delay, iteration-count, direction, fill-mode, and play-state. The ones that trip people up are fill-mode and direction.

fill-mode: forwards means 'hold the final keyframe state after the animation ends.' Without it, your element snaps back to its original style the moment the animation finishes — which looks like a bug but is actually the spec-correct default. animation-direction: alternate makes the animation play forward then backward on odd/even iterations, which is perfect for pulse or breathe effects without needing to mirror your keyframes manually.

NotificationBadge.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@keyframes pulse-ring {
  0% {
    transform: scale(1);
    opacity: 0.75;
  }
  70% {
    transform: scale(1.6);
    opacity: 0;
  }
  100% {
    transform: scale(1.6);
    opacity: 0;
  }
}

.live-indicator {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.live-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background-color: #22c55e;
}

.live-dot::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: #22c55e;

  animation: pulse-ring 1.5s ease-out 0s infinite;
}

@media (prefers-reduced-motion: reduce) {
  .live-dot::before {
    animation: none;
  }
}
Output
Visual result:
• A 12px green circle with a translucent clone behind it
• The clone expands from 12px → ~19px and fades from 75% → 0% opacity
• At 1.5s intervals it resets and repeats — the classic 'live' pulse
• On devices with prefers-reduced-motion: reduce → static green dot, no animation
Watch Out: animation-fill-mode Gotcha
If you animate an element's opacity from 0 to 1 (a fade-in) and forget animation-fill-mode: forwards, the element snaps back to opacity: 0 the instant the animation ends. The fix is always animation-fill-mode: forwards on one-shot animations, or structure your default styles so the element is already at its 'final' state and use animation-direction: reverse to animate in.
Production Insight
Remember: animation-fill-mode only applies after the animation completes.
During the initial delay, you need fill-mode: backwards or both to hide the element.
Production bug: element visible before delay ends because fill-mode missing.
Key Takeaway
Use animation-fill-mode: both for entrance animations.
This applies the from keyframe during delay (keeps hidden)
and the to keyframe after end (keeps final state).

Sequencing and Staggering — Making Multiple Elements Dance Together

A single animated element is fine. A whole list of cards that cascade in one by one feels premium. The technique is called staggering: each element gets the same animation, but with a progressively increasing animation-delay.

The cleanest way to stagger in plain CSS is with the --stagger-index CSS custom property (CSS variable) set inline per element. Your JavaScript or your templating engine sets the index as an inline style attribute. The CSS then reads it and calculates the delay with calc(). This keeps your animation logic in CSS where it belongs, while letting your data layer drive the count.

This pattern is used everywhere: Vercel's dashboard on load, GitHub's contribution graph, Notion's sidebar items. Once you see it, you can't unsee it. The key insight is that the delays are relative to when the animation starts, so if the container has a 200ms entrance delay, all children should account for that in their own delays.

When sequencing is more complex — a loading screen with a logo, then a tagline, then a call-to-action button appearing in order — use explicit delay values rather than a formula. Explicit is clearer and easier to tune during design review.

StaggeredCardList.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@keyframes card-enter {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.feature-card {
  opacity: 0;

  animation:
    card-enter
    400ms
    cubic-bezier(0.16, 1, 0.3, 1)
    calc(var(--stagger-index, 0) * 80ms)
    both;
}

@media (prefers-reduced-motion: reduce) {
  .feature-card {
    animation: none;
    opacity: 1;
  }
}
Output
Visual result (4 cards):
• t=0ms → Card One fades+slides up (400ms duration)
• t=80ms → Card Two begins entrance
• t=160ms → Card Three begins entrance
• t=240ms → Card Four begins entrance
• t=640ms → All cards fully visible
• cubic-bezier(0.16,1,0.3,1) gives snappy entry with soft landing
• With prefers-reduced-motion → all 4 cards appear instantly
Interview Gold: CSS Variables + calc() for Staggering
Interviewers love this pattern because it shows you understand CSS custom properties as data, not just theming. Explain that --stagger-index is set by the server or JS loop, calc() computes the delay in CSS, and zero JavaScript is required to drive the timing. That separation of concerns is the senior-developer answer.
Production Insight
Stagger delays accumulate quickly: 50 cards * 80ms = 4s to complete.
That's too slow for most pages — users will start interacting before all cards settle.
Rule: cap total stagger duration at 1s and use time-based indices (e.g., 50ms for first 8 items, then 100ms for remaining).
Key Takeaway
Stagger with calc() and CSS custom properties.
But cap the total duration to avoid sluggish entrance.
Always test on a slow connection — delays feel longer after pageload.

Performance: The Compositor Thread and Why `transform` is King

The browser renders a page in four main stages: Style (calculates styles), Layout (calculates geometry), Paint (fills pixels), and Composite (blits layers together). When you animate a property that triggers Layout or Paint, the browser must recalculate those stages on every frame — that's expensive and causes jank.

Properties like transform and opacity only trigger the Composite stage. They run on the compositor thread, which is separate from the main thread. The main thread handles JavaScript, style calculation, layout, and paint. The compositor thread runs on the GPU and can handle transform and opacity changes at 60fps without blocking the main thread. That's why you should always replace width with transform: scaleX(), top/left with transform: translate(), and margin with transform or padding changes.

will-change is a CSS property that tells the browser to prepare for changes to a property. Using will-change: transform before an animation starts can prevent jank by promoting the element to its own compositor layer. But don't overuse it — each promoted layer consumes GPU memory. Apply it right before the animation starts and remove it after (or use JavaScript to add/remove the class).

CompositorOptimisation.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* ── Before: animating width causes layout recalc ── */
.bad-progress {
  width: 0%;
  transition: width 300ms ease-out;
}
.bad-progress.filled {
  width: 100%;
}

/* ── After: use scaleX — no layout, compositor-only ── */
.good-progress {
  overflow: hidden;
  position: relative;   /* contain the scaled child */
  width: 100%;
  height: 4px;
}
.good-progress .bar {
  height: 100%;
  background-color: #22c55e;
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 300ms ease-out;
}
.good-progress.filled .bar {
  transform: scaleX(1);
}

/* ── Use will-change sparingly ── */
.good-progress .bar {
  will-change: transform;
  transition: transform 300ms ease-out;
}
.good-progress.filled .bar {
  will-change: auto;  /* remove after transition ends */
}

/* Alternative: promote via translateZ */
.good-progress .bar {
  transform: translateZ(0);  /* force compositor layer */
  transition: transform 300ms ease-out;
}
Output
Visual result:
• Both bars fill from left to right, but the 'bad' version recalculates layout on every frame
• The 'good' version uses scaleX and runs entirely on compositor — zero layout, zero paint
• On low-end devices, the bad bar drops to 20fps; the good bar stays at 60fps
• will-change: transform pre-promotes to a layer, removing potential jank on start
Overusing will-change Hurts Performance
Each element with will-change consumes GPU memory. Don't apply it to dozens of elements at once — you'll exhaust the layer limit and cause battery drain. Apply it only to elements that will animate soon, and remove it after the animation completes. A safer default is transform: translateZ(0) which also promotes without memory cost, but avoid using it on elements that are never animated.
Production Insight
Promoted layers increase GPU memory usage — Chrome has an upper limit (~256 layers).
If you promote too many elements, the browser starts combining layers on CPU, causing opposite effect.
Rule: promote only the currently animating elements, then demote after animation ends.
Key Takeaway
Only transform and opacity skip layout/paint.
Compositor thread keeps animations at 60fps.
Use will-change sparingly and only during the animation.

Real-World Patterns: Hover Effects, Loaders, and Page Transitions

Now let's combine what you've learned into three production-ready patterns.

Pattern 1: Micro-interaction hover effect — a card that lifts on hover, with a subtle shadow and border color shift. Use transition on transform, box-shadow, border-color. Duration 150ms ease-out for snappy feedback.

Pattern 2: Infinite loading spinner — a @keyframes animation that rotates continuously. Use animation: spin 1s linear infinite; to avoid the eye-strain of easing curves. For accessibility, reduce motion users should see a static spinner (or hide it).

Pattern 3: Page transition — when navigating between views in a SPA, fade out current content and fade in new content. Use @keyframes on a container with animation-fill-mode: both. Coordinate delays with JavaScript to match route change timing.

Key to all patterns: test on real devices, especially low-end Android. A hover animation that's 60fps on an iPhone 15 might be 15fps on a Moto G. Always throttle CPU in DevTools.

RealWorldPatterns.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* ── Pattern 1: Card hover lift ──────────────────────────────── */
.card {
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
  transition:
    transform 150ms ease-out,
    box-shadow 150ms ease-out,
    border-color 150ms ease-out;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0,0,0,0.10);
  border-color: #cbd5e1;
}

/* ── Pattern 2: Infinite spinner ─────────────────────────────── */
@keyframes spin {
  to { transform: rotate(360deg); }
}
.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid #e2e8f0;
  border-top-color: #4f46e5;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
    opacity: 0.5;
  }
}

/* ── Pattern 3: Page fade transition ─────────────────────────── */
@keyframes fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@keyframes fade-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.page-exit {
  animation: fade-out 200ms ease-out forwards;
}
.page-enter {
  animation: fade-in 400ms ease-out 200ms both;
}
Output
Visual result:
• Card lifts 4px with soft shadow on hover (150ms)
• Spinner rotates continuously with linear timing (0.8s loop)
• Page transition: exit fades out in 200ms, then new page fades in from below with 200ms delay
• All respect prefers-reduced-motion via disabled animation and static state
The Motion Design Hierarchy
  • Feedback: hover, click — 80-200ms, subtle (transform/y-axis change)
  • Attention: pulsing dots, loading spinners — infinite, subtle opacity/scale
  • Spatial orientation: page transitions, modals — 300-600ms, combine opacity + translate
  • Always reduce motion for people with vestibular disorders — it's not optional
Production Insight
Over-engineering micro-interactions is a real trap.
Each extra transition adds ~1ms of style recalc per frame on mid-range devices.
Rule: only animate properties that change user perception — don't animate every property just because you can.
Key Takeaway
Three patterns cover 90% of motion needs.
Keep durations short for interactivity, longer for transition.
Test on low-end devices — what works on a MacBook may not work on a Moto G.
● Production incidentPOST-MORTEMseverity: high

Dashboard Progress Bar Causes Mobile Jank — The width Animation Trap

Symptom
Progress bar width animation caused repeated forced layout recalculations on every frame. On low-end devices, the browser could not keep up with 60fps, resulting in visible stutter and dropped frames.
Assumption
The team assumed that animating width from 0% to 100% with a CSS transition was the simplest and most reliable approach. They didn't test on anything slower than a recent iPhone.
Root cause
Animating width triggers layout in every frame: the browser must recalculate the positions and sizes of all sibling elements because width changes affect the document flow. On devices with limited CPU (common on Android under $300), this layout work exceeds the 16ms budget per frame.
Fix
Replaced width animation with transform: scaleX() on a nested inner element, using transform-origin: left to grow from left to right. The outer container remained at full width. This moved the animation to the compositor thread, eliminating layout recalculations.
Key lesson
  • Never animate layout-triggering properties (width, height, margin, top/left) in performance-critical UI sections.
  • Use transform and opacity as replacements — they skip layout and paint and run on the GPU compositor thread.
  • Always test animations on low-end devices or use CPU throttling in DevTools (Chrome: Performance tab -> CPU: 6x slowdown).
  • Set a performance budget for animation: if you exceed 10ms of layout/paint per frame, find a compositor-only alternative.
Production debug guideA step-by-step guide for identifying and fixing animations that cause visible stutter or high layout/paint costs.3 entries
Symptom · 01
Animation stutters or drops frames, especially on mobile or during scrolling.
Fix
Open Chrome DevTools > Performance tab > Record a session while animation runs. Look for red bars marked 'Layout' or 'Paint' above the flame chart — they indicate forced layout or paint work per frame.
Symptom · 02
Framerate consistently below 60fps in the FPS meter (DevTools > Rendering > FPS Meter).
Fix
Enable 'Paint flashing' and 'Layer borders' in DevTools > Rendering. If the animated element shows a green border, it's in its own compositor layer — good. If it doesn't, add will-change: transform to promote it.
Symptom · 03
Large Layout Shift (CLS) observed in Lighthouse or Core Web Vitals report.
Fix
Check if the animated element is in normal document flow. If so, set position: absolute or position: fixed to remove it from layout. Also verify you're not animating width/height of a non-positioned element.
★ Quick Debug Cheat Sheet for CSS Animation JankCommon animation performance problems and immediate commands to diagnose and fix them. Run these commands in Chrome DevTools console or the Rendering panel.
Animation stutters during scroll
Immediate action
Open DevTools → Rendering → Enable 'Scrolling Performance Issues' and check for red highlights.
Commands
In console: `performance.now()` to get baseline timestamp; then run animation and check time difference.
Profile: `console.profile('animation'); setTimeout(() => console.profileEnd(), 2000);`
Fix now
Add will-change: transform to the element. If still janky, reduce the number of concurrently animating elements.
Element flashes visible before animation starts+
Immediate action
Check if the element has `opacity: 0` set as initial style and ask: is there a delay?
Commands
In Elements panel, inspect computed styles. Verify `animation-fill-mode` is set.
In console: `getComputedStyle(element).animationFillMode`
Fix now
Add animation-fill-mode: both to the element's animation shorthand or separate property.
Animation runs choppy on mobile but smooth on desktop+
Immediate action
Open DevTools → Settings → on 'Devices' tab add a low-end mobile device profile. Reload and record performance.
Commands
Enable CPU throttling: DevTools → Performance → CPU dropdown → select 6x slowdown.
Toggle 'Paint flashing' in Rendering panel to see if the animated element causes paint invalidation.
Fix now
Switch to animating only transform and opacity. Use transform: translateZ(0) to promote to compositor layer.
When to Use CSS Transitions vs. CSS @keyframes Animations
Feature / AspectCSS TransitionCSS @keyframes Animation
Trigger required?Yes — needs a state change (hover, class toggle, focus)No — runs on page load automatically if applied
Number of waypoints2 only: start state → end stateUnlimited: 0%, 25%, 50%, 75%, 100% etc.
Looping supportNo — one-shot per state changeYes — animation-iteration-count: infinite
Best forInteractive feedback (hover, click, focus)Autonomous motion (loaders, pulses, entrances)
Direction controlAlways plays in reverse on state revertControlled via animation-direction: alternate / reverse
Performance ceilingSame — both use compositor thread for transform+opacitySame — both use compositor thread for transform+opacity
Pause/resume controlNot possible in CSS aloneYes — animation-play-state: paused / running
Code complexitySimple — 1 property, 4 sub-valuesHigher — requires @keyframes block + animation shorthand

Key takeaways

1
Put transition on the base state, never on :hover
this ensures the animation plays in both directions automatically.
2
Only animate transform and opacity for performance-critical motion
they're the only two properties that skip layout and paint and run entirely on the GPU compositor thread.
3
`animation-fill-mode
both is the correct default for entrance animations — backwards hides the element during the delay, forwards` holds the final frame after completion.
4
Always include a `@media (prefers-reduced-motion
reduce)` block — this is a functional accessibility requirement, not optional polish, and it's increasingly checked in code reviews and audits.
5
Use will-change sparingly and only during animations
over-promoting layers exhausts GPU memory and can degrade performance rather than improve it.
6
Test animations on low-end devices or use CPU throttling in DevTools
a 60fps experience on a MacBook can be 15fps on a mid-range Android phone.

Common mistakes to avoid

4 patterns
×

Putting `transition` on the hover state instead of the base state

Symptom
The hover-in animation plays smoothly, but when the cursor leaves the button it snaps back instantly with no transition
Fix
Always put transition on the base element selector (.button), not on .button:hover. The hover state should only declare the destination values.
×

Animating `width`, `height`, `top`, `left`, or `margin`

Symptom
Animation runs at 60fps on your MacBook but drops to 20fps on a mid-range Android phone, causing visible stutter
Fix
Replace layout-triggering properties with transform equivalents. Use transform: scaleX() instead of animating width. Use transform: translate() instead of animating top/left. These two properties are the only ones guaranteed to stay on the GPU compositor thread.
×

Forgetting `animation-fill-mode: both` on entrance animations

Symptom
During the delay period before a staggered card animates in, it flashes visible at full opacity, then disappears, then fades back in
Fix
fill-mode: both applies the from keyframe values during the delay (keeping the element hidden/offset) and holds the to keyframe after completion (keeping the element visible). Make this your default for any entrance animation.
×

Not including a `prefers-reduced-motion` override

Symptom
Users with vestibular disorders experience nausea or discomfort from looping animations and have no way to disable them
Fix
Always add a @media (prefers-reduced-motion: reduce) block that either disables the animation (animation: none) or replaces it with a static visual. This is a WCAG requirement (Success Criterion 2.3.3).
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What's the difference between CSS transitions and CSS animations, and ho...
Q02SENIOR
Why should you prefer animating `transform` and `opacity` over propertie...
Q03SENIOR
If a CSS animation has `animation-delay: 500ms` but the element is visib...
Q04SENIOR
How does `will-change` affect rendering performance, and when should you...
Q01 of 04JUNIOR

What's the difference between CSS transitions and CSS animations, and how do you decide which one to reach for?

ANSWER
Transitions are declarative animations between two states triggered by a property change (e.g., hover, class toggle). Animations use @keyframes to define multi-step timelines that can loop, pause, and play in reverse. My rule of thumb: if it's interactive feedback (hover, click, focus), use transition. If it's autonomous motion (loader, entrance sequence, pulsing dot), use animation.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Can I use CSS transitions and animations together on the same element?
02
Why does my CSS animation cause layout shift and hurt my CLS score?
03
What's the difference between animation-iteration-count: infinite and just making a very long animation duration?
🔥

That's HTML & CSS. Mark it forged?

5 min read · try the examples if you haven't

Previous
Responsive Design and Media Queries
6 / 16 · HTML & CSS
Next
CSS Variables and Custom Properties