CSS blocks rendering: until the browser has downloaded and parsed all the stylesheets linked in the head, it paints nothing. It is a sensible choice, because showing an unstyled page and then redrawing it would be worse. The problem arises when that CSS weighs hundreds of kilobytes, of which the page uses only a small part. It almost always happens with generic themes, complete frameworks included in full, and years of accumulated styles from which nothing was ever removed.
Measuring unused CSS
In Chrome DevTools the «Coverage» panel records which bytes of CSS and JavaScript are actually used. You open it from the command menu, start recording and reload the page. The result is a table with the percentage of unused code for each file and, clicking on a file, the lines highlighted in red.
Two cautions when interpreting the numbers:
- coverage only concerns what happened during recording. The styles of an open menu, a form with errors, a modal window or another screen width show up as unused if they were not triggered;
- a stylesheet used at 30% on one page may be used at 70% on another. You have to reason about the set of pages, not just one.
So it is worth recording, interacting with the page (opening menus, resizing, filling in forms) and repeating on several page templates.
Removing CSS you do not need
At the source
The cleanest route is not to include it. Many frameworks let you import only the modules you use: grid, typography and buttons, without carousels, tooltips and components you never use. In projects with preprocessors it is enough to comment out the unnecessary imports in the main file.
With removal tools
Tools such as PurgeCSS scan the project's HTML files and templates, collect the class names and selectors present, and remove from the CSS those that do not appear. The risk is removing classes generated dynamically by JavaScript, for example:
element.classList.add('state-' + state); // 'state-active', 'state-error'
The tool does not see «state-active» in the code and removes it. That is why there are safelists in which to declare classes or patterns to keep:
safelist: [/^state-/, 'open', 'visible']
Utility libraries such as Tailwind take the opposite approach: they generate only the classes actually found in the sources, so the problem arises less often, provided you do not build class names by concatenating strings.
Extracting critical CSS
Critical CSS is the minimal set of rules needed to paint the visible part of the page on load: header, menu, heading, first block of content. The idea is:
- inline these rules in a
styletag in the HTML, so no network round trip is needed to show them; - load the rest of the CSS without blocking rendering;
- let the full stylesheet arrive and apply shortly afterwards.
A common way to load the non-critical stylesheet:
<style>/* inlined critical rules */</style>
<link rel="preload" href="/css/full.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/full.css"></noscript>
There are tools that extract critical rules automatically by opening the page in a headless browser at one or more viewport sizes.
When it is not worth it
Critical CSS has a cost: inlined rules are not cached and are repeated in every HTML page. If the full stylesheet is already small, say a few tens of kilobytes compressed, and is served with good caching, the added complexity is often not worth the gain. Moreover, if the extracted critical CSS does not properly cover the visible part, a layout shift occurs when the full stylesheet arrives, worsening CLS.
Splitting CSS by page or component
An intermediate alternative, often easier to maintain, is to split the CSS: a small common file with the base rules, and separate files for specific sections (shop, blog, members' area) loaded only where needed. With HTTP/2 a few extra files are not a problem, and each page downloads only what it uses.
Other waste to check
@importinside CSS: it creates sequential download chains. It is better to link files directly in the HTML or merge them at build time.- CSS for print or for distant media queries: by linking it with the
media="print"attribute, the browser downloads it at low priority and without blocking rendering. - Base64 fonts and images in CSS: they bloat the blocking file with data that could arrive in parallel.
In short
- CSS in the head blocks rendering: the less there is, the sooner the page appears.
- The Coverage panel shows unused CSS, but it must be read across several pages and interactions.
- Remove it at the source or with cleanup tools, protecting dynamic classes with a safelist.
- Inlined critical CSS speeds up the first render, but is not always worth the complexity.
- Avoid
@import, separate print CSS and split by section where it makes sense.