All articles
Performance

12 CSS Performance Tips That Actually Make a Difference

7 October 2024 9 min read Performance

Cut render-blocking CSS, reduce unused styles, use contain and content-visibility, optimise selectors and squeeze every millisecond of rendering performance from your stylesheet.

CSS is often treated as inherently cheap. But at scale — large stylesheets, complex layouts, heavy animations — CSS can absolutely tank page performance. These 12 techniques are the ones that move the needle in real projects.

1–3: Critical CSS and delivery

1. Inline critical CSS

Critical CSS is the styles needed to render above-the-fold content. Inline it in <style> tags in the <head> and load the rest asynchronously:

<style>/* Inlined critical CSS */</style>
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">

2. Remove unused CSS

Tools like PurgeCSS or the built-in Coverage panel in Chrome DevTools will show you exactly which rules are never used. In a typical project, 60–70% of CSS goes unused.

3. Use layer for specificity management

@layer reset, base, components, utilities;

@layer reset { /* normalize */ }
@layer base   { /* typography, body */ }
@layer components { /* cards, buttons */ }
@layer utilities  { /* .sr-only, .truncate */ }
4–6: Rendering performance

4. content-visibility: auto

.below-fold-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* estimated height */
}

The browser skips rendering off-screen sections entirely until they're about to enter the viewport. On long pages, this can reduce initial render time by 50%+.

5. CSS contain

.widget {
  contain: layout style paint;
}
/* Or the shorthand */
.widget {
  contain: strict;
}

Tells the browser that changes inside the element won't affect outside, enabling aggressive optimisation.

6. Avoid layout-triggering properties in animations

Never animate: width, height, top, left, margin, padding. Only animate transform and opacity.

7–9: Selector efficiency

7. Avoid universal selectors in deep trees

/* ✗ Slow — matches everything, then filters */
.card * { color: inherit; }

/* ✓ Fast — specific target */
.card p, .card span { color: inherit; }

8. Avoid :nth-child() on large lists

Pseudo-class selectors on large dynamic lists cause style recalculation on every DOM change. Add a class directly to the element instead.

9. Keep selector depth shallow

CSS selectors are matched right-to-left. Deep selectors like .page .section .card .body p require the browser to walk up five levels for every p in the document.

10–12: File size and loading

10. Minify and compress

Gzip cuts CSS file size by 60–80%. Brotli cuts it further. Enable on your server (most hosting does this automatically).

11. Preload web fonts

<link rel="preload" href="/fonts/syne.woff2" as="font" type="font/woff2" crossorigin>

Fonts are late-discovered resources. Preloading them prevents flash of unstyled text (FOUT).

12. font-display: swap

@font-face {
  font-family: 'Syne';
  src: url('/fonts/syne.woff2') format('woff2');
  font-display: swap; /* Show fallback immediately, swap when loaded */
}