Back to Performance
Performance
medium
mid

How do you optimize CSS delivery and size for performance?

Ship less CSS (purge unused, split per route, critical-CSS inline + defer rest), keep selectors flat and cheap, avoid render-blocking and @import chains, animate only transform/opacity, use containment (`content-visibility`, `contain`) to limit layout/paint scope, and minify + compress + cache. CSS is render-blocking, so its size and delivery directly affect FCP/LCP.

6 min read·~10 min to think through

CSS performance has two fronts: delivery (CSS is render-blocking, so its size/timing affects first paint) and runtime (style/layout/paint cost while the page is alive).

Delivery — get pixels on screen faster

  • Ship less CSS. Purge unused rules (PurgeCSS, or Tailwind's JIT). Big frameworks ship far more than you use.
  • Critical CSS. Inline the above-the-fold styles in <head>, load the rest asynchronously (<link rel="preload" ... onload>). FCP no longer waits for the full stylesheet.
  • Code-split CSS per route/component so a page only downloads what it needs.
  • Avoid @import in CSS — it serializes downloads (request waterfall). Use <link> or a bundler.
  • Minify, compress (Brotli/gzip), and cache with long Cache-Control + content hashing.
  • Put <link rel="stylesheet"> in the <head> (render-blocking — but you want it early); preconnect/preload fonts and key CSS.

Selectors — keep them cheap and flat

  • The browser matches selectors right-to-left.nav ul li a checks every <a> first. Keep selectors shallow.
  • Prefer single class selectors (this is what BEM, CSS Modules, utilities all give you).
  • Avoid the universal selector in expensive contexts and deep descendant chains.
  • (In practice, selector matching is rarely the bottleneck on modern engines — layout/paint usually is — but flat selectors also help maintainability.)

Runtime — minimize layout & paint work

  • Animate only transform/opacity — compositor-only, no reflow/repaint.
  • Avoid layout thrashing — don't interleave layout reads and DOM writes.
  • CSS containment: contain: layout paint and content-visibility: auto tell the browser a subtree's layout/paint can be skipped or scoped — huge wins for long pages and offscreen content.
  • Reduce paint cost: simpler shadows, fewer gradients, smaller blur.
  • Reserve space (image dimensions, aspect-ratio) to avoid layout shifts.

Fonts (CSS-adjacent)

  • font-display: swap (or optional), preload key fonts, subset them, use variable fonts.

Measure

Lighthouse, Coverage tab (shows unused CSS), the Performance panel for style/layout/paint time.

Senior framing

The senior framing separates delivery vs runtime and leads with the biggest lever: CSS is render-blocking, so shipping less and inlining critical CSS moves FCP/LCP the most. Then the modern runtime tools — content-visibility and contain — which most candidates don't mention. "Optimize selectors" alone is a dated, low-impact answer.

Follow-up questions

  • What is critical CSS and how do you extract it?
  • What does `content-visibility: auto` do?
  • Why is `@import` in CSS bad for performance?
  • Why are selectors matched right-to-left?

Common mistakes

  • Focusing only on selector optimization (low impact today).
  • Using @import chains in CSS.
  • Shipping a whole UI framework's CSS unpurged.
  • Animating layout properties.

Edge cases

  • Critical CSS extraction must be re-run when styles change — automate it.
  • content-visibility: auto can cause scrollbar jumpiness if intrinsic size isn't hinted.
  • Over-splitting CSS can create too many small requests.

Real-world examples

  • Inlining critical CSS on landing pages, purging Tailwind/Bootstrap, content-visibility on long feeds.

Related questions