How do you optimize CSS 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.
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
@importin 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/preloadfonts and key CSS.
Selectors — keep them cheap and flat
- The browser matches selectors right-to-left —
.nav ul li achecks 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 paintandcontent-visibility: autotell 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(oroptional), 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.