Fixing Unused CSS Without Breaking Your Site
Unused CSS is style rules that download with a page but never match anything on it. A little is normal, because one stylesheet usually serves many pages. It becomes a problem when most of a large file is unused and still has to arrive before the page can appear.
The trap is that "unused on this page right now" is not the same as "unused anywhere": menus, error messages, logged-in views and pop-ups often look unused to a quick test. This guide shows how to measure it properly, remove rules that are truly dead, and automate trimming without losing the ones that only appear later.
Why unused CSS matters, and when it does not
Every byte of CSS in a stylesheet linked from the page head has to download and be parsed before the browser draws anything. Unused rules also add work when the browser matches selectors against elements. On a fast laptop that cost is small; on a mid-range phone on a patchy mobile connection it shows up as a longer blank screen.
Whether it is worth your time depends on three things:
| Situation | Priority | Reason |
|---|---|---|
| A large stylesheet blocks rendering and most of it is unused on key pages | High | Visitors wait for bytes they never use |
| A whole framework is loaded for a few components | High | Often the single biggest source of dead rules |
| A modest shared stylesheet is partly unused on each page but used across the site | Low | It is cached after the first visit and splitting may cost more than it saves |
| Unused CSS loads after first paint | Low | It does not hold up the first view |
The CSS Checker estimates how much of your CSS goes unused. Read that estimate together with file size and whether the file is render-blocking: a high percentage of a tiny file is not worth a day's work, while a moderate percentage of a very large blocking file might be. If you have not yet dealt with render-blocking, Fixing render-blocking CSS is often the better first job.
Where unused CSS comes from
Knowing the source tells you the safest fix. Most sites have a mix of these:
- Frameworks loaded whole. A full UI framework stylesheet brings grids, forms, modals, carousels and utilities for every possible layout, even if you use a handful.
- Icon and font libraries. An icon font stylesheet can contain a rule for every icon in the set, while the site uses a dozen.
- Old features nobody removed. Styles for a promotion that ended, a redesign that replaced a component, or a widget that was uninstalled.
- Plugin and app styles loaded everywhere. A contact form or slider plugin that adds its CSS to every page, not just the one with the form or slider.
- Theme options you never switched on. Commercial themes ship styles for every layout variant.
- Duplicate rules. The same component styled twice, once in an old file and once in a new one, where only the second copy matters.
The first two are best fixed by importing only the parts you use. The middle three are best fixed by deleting or conditionally loading. Duplicates are fixed by merging. Automated trimming tools help with all of them but are a safety net, not a replacement for understanding what is there.
Measuring unused CSS properly
The Coverage panel
In Chrome or Edge, open developer tools, open the command menu with Ctrl+Shift+P (or Cmd+Shift+P on a Mac), type "coverage" and choose Show Coverage. Click the reload button in the panel. You get a list of every CSS and JavaScript file with its total size and the bytes that went unused. Filter the list to CSS. Clicking a file opens it in the Sources panel with a coloured bar beside each rule: one colour for rules that matched, another for rules that did not.
That view is where most people stop, and it is where the mistakes start. Coverage records what matched while you were watching. If you did not open the mobile menu, the menu styles are "unused". If you did not submit a form with an error, the error styles are "unused". If you were logged out, the account area styles are "unused".
Recording a realistic session
Keep the Coverage panel recording while you work through a checklist on each key template:
- Load the page at a phone width, then resize to tablet and desktop widths so each media query applies.
- Open and close every menu, accordion, tab set, modal and cookie banner.
- Hover and focus links, buttons and form fields, including keyboard focus.
- Submit forms empty to trigger validation messages, and complete one to see the success state.
- Switch colour scheme if you support dark mode, and turn on reduced motion if you have rules for it.
- Repeat logged in, with items in a basket, or in any other state that changes the page.
Export the coverage result from the panel so you can compare it with later runs.
Automating the measurement
For larger sites, a headless browser can collect CSS coverage for a list of URLs and a script of interactions. Puppeteer and Playwright both expose CSS coverage in Chromium, so you can combine many pages into one list of selectors that were used somewhere. That combined list is far more trustworthy than a single page view.
// coverage.mjs: collect used CSS ranges across several pages (Puppeteer)
import puppeteer from 'puppeteer';
const urls = [
'https://example.co.uk/',
'https://example.co.uk/services/',
'https://example.co.uk/contact/'
];
const browser = await puppeteer.launch();
const page = await browser.newPage();
const results = [];
for (const url of urls) {
await page.coverage.startCSSCoverage();
await page.goto(url, { waitUntil: 'networkidle0' });
await page.click('.menu-toggle').catch(() => {});
results.push(...await page.coverage.stopCSSCoverage());
}
await browser.close();
console.log(JSON.stringify(results.map(r => ({ url: r.url, ranges: r.ranges })), null, 2));
The fix, step by step
Step 1
Measure coverage on your key templates
Run the CSS Checker to see which stylesheets carry the most unused CSS and how large they are. Then open the Coverage panel in Chrome developer tools and reload each key template: home, a main service or product page, a category or listing page, a blog post and the contact or checkout page. Note each stylesheet, its total size and its unused bytes, and focus first on large files that block rendering.
Step 2
Record the interactive states too
Keep coverage recording while you resize through phone, tablet and desktop widths, open menus and modals, focus fields with the keyboard, trigger form errors and success messages, and view the site logged in if that changes anything. Rules that only appear in these states will otherwise look unused. For larger sites, script the same journey in Puppeteer or Playwright and merge the results so you have one list of rules that were used anywhere.
Step 3
Sort the unused rules by where they came from
Group the unused rules into frameworks loaded whole, icon or font libraries, plugin or app styles, theme options you do not use, duplicates and your own leftovers. The group decides the fix. Frameworks and icon sets usually allow importing only the parts you use. Plugin styles can often be loaded only on the pages that need them. Your own leftovers and duplicates can simply be deleted once you have confirmed they are dead.
Step 4
Delete dead rules at source
Search the whole codebase, including templates, JavaScript and CMS content, for each class name before removing a rule, because a class can be added by a script or typed into a page editor. Remove confirmed dead rules from the source files, not from compiled output, so the next build does not bring them back. Replace full framework imports with the individual modules you use, and swap icon fonts for inline SVG or a subset.
Step 5
Automate trimming with a safelist
Add
PurgeCSS to your build, or rely on your utility framework's own source scanning, and point it at every
file that can contain a class name, including JavaScript and templates. Build a safelist
for classes that are created at runtime, such as is-open, has-error, state
classes from sliders and anything assembled from strings. Compare the trimmed file size with the original
and keep the report for the next review.
Step 6
Test the pages and states you recorded
Work through the same checklist you used for coverage on the trimmed build: every template, every screen width, menus, modals, form errors, logged-in views and dark mode. Compare screenshots before and after where you can. Anything that lost its styling points to a missing safelist entry or a content path the tool did not scan. Then run the CSS Checker again and confirm the unused share has fallen without new visual problems.
Setting up PurgeCSS safely
PurgeCSS reads your content files, extracts every string that looks like a class name, and removes rules whose selectors do not match any of them. It works as a PostCSS plugin, a command-line tool, or through plugins for common bundlers.
// postcss.config.mjs
import purgecss from '@fullhuman/postcss-purgecss';
export default {
plugins: [
purgecss({
content: [
'./src/**/*.html',
'./src/**/*.{js,jsx,ts,tsx,vue}',
'./templates/**/*.twig'
],
safelist: {
standard: ['is-open', 'is-active', 'has-error', /^swiper-/],
deep: [/^prose$/],
greedy: [/tooltip/]
},
keyframes: true,
fontFace: true
})
]
};
The three safelist types behave differently. standard keeps selectors that match; deep
also keeps everything nested inside a matching parent, which suits rich-text areas where editors can
use any element; greedy keeps any selector that contains a match anywhere. The keyframes
and fontFace options remove unused animations and font declarations as well as rules.
Protecting blocks in the stylesheet
For a section of CSS that must never be trimmed, such as styles for content pasted in by editors, wrap it in special comments:
/* purgecss start ignore */
.wysiwyg table { border-collapse: collapse; }
.wysiwyg blockquote { border-left: 4px solid #cbd5e1; }
/* purgecss end ignore */
The dynamic class problem
PurgeCSS and similar tools read your files as text. They cannot see a class name that only exists once code runs. This pattern will lose its styles:
// Class assembled at runtime: the tool never sees "alert-warning"
el.className = 'alert-' + level;
Either write the full names somewhere the tool can see them, for example in a lookup object such as
{ warning: 'alert-warning', error: 'alert-error' }, or add them to the safelist. The same
applies to classes chosen in a CMS, returned from an API, or added by third-party scripts.
Platform and framework notes
Utility-first frameworks
Tailwind CSS generates only the classes it finds in your source files, so unused utilities are not
the problem there. What goes wrong is detection: class names built from string fragments, content in
folders the scanner does not look at, or files excluded by your ignore rules. Recent versions detect
sources automatically and let you add paths with the @source directive; older versions use
the content array in the config file. In both cases the fix for a missing style is to write
the complete class name somewhere the scanner reads.
WordPress
Much WordPress unused CSS comes from plugins that load styles on every page. Performance plugins offer "remove unused CSS" features that generate trimmed CSS per page; they work, but test logged-in views, forms and WooCommerce pages carefully, and clear their cache after any design change. Loading plugin styles only where needed is often cleaner; see CSS in WordPress themes.
React and other component frameworks
Component-scoped CSS, such as CSS Modules, tends to be split along with the components that import it, so pages only load what their components need. Leftover global stylesheets and whole UI libraries are the usual source of unused CSS in these apps; the React and Next.js guide covers this.
Static sites with no build step
You can still run PurgeCSS once from the command line against your HTML files and stylesheet, review the output, and upload the trimmed file. Keep the original so you can compare and restore.
Mistakes that break sites
- Trusting one page view. Deleting everything Coverage marks red on the home page removes styles for every other template.
- Forgetting states. Menus, error messages, empty baskets, cookie banners and print styles are the usual casualties.
- Not scanning JavaScript. If your content globs cover only HTML, every class added by a script disappears.
- Trimming third-party widget CSS. Embedded booking, chat or review widgets often add their own markup after load; their styles look unused to static tools.
- Editing compiled output. Changes to a built file are overwritten by the next build. Fix the source.
- Treating it as done. New features add CSS. Recheck after each significant release.
1. How much unused CSS is acceptable?
There is no official threshold. Some unused CSS on every page is normal, because one stylesheet serves many templates and states. Focus on the combination of size and timing: a large render-blocking stylesheet where most of the bytes go unused on your key pages is worth fixing, while a small cached stylesheet that is partly unused on each page usually is not. Compare your key templates, not just the home page, before deciding how far to go.
2. Why does the Coverage panel say most of my CSS is unused?
Coverage only records rules that matched during the time you were watching, on that one page, at that screen width, in that state. Styles for other pages, mobile or desktop layouts you did not view, menus you did not open, form errors you did not trigger and logged-in areas all count as unused. The figure is a useful starting point, but record a full session across widths and states before treating anything as genuinely dead.
3. Is PurgeCSS safe to use on a live site?
It is safe when it is set up carefully and tested. The risk comes from class names it cannot see, such as ones added by JavaScript, built from strings, chosen in a CMS or injected by third-party widgets. Point it at every file that can contain class names, safelist runtime classes and editor content areas, and test every template and interactive state on the trimmed build before deploying. Keep the untrimmed stylesheet so you can roll back quickly.
4. Does unused CSS affect SEO?
Not directly. Search engines do not penalise a page for carrying style rules it does not use. The effect is indirect: a large render-blocking stylesheet can delay the first paint and the main content appearing, which affects how visitors experience the page and can show up in speed measurements such as Largest Contentful Paint. Remove unused CSS because it makes pages faster for real people, especially on phones, not because of a ranking rule.
5. Why did styles disappear after I removed unused CSS?
The trimming tool removed rules for class names it could not find in the files it scanned. Common causes are classes added by JavaScript, class names assembled from fragments, content folders missing from the scan paths, classes typed into a page editor and markup injected by third-party widgets. Find the affected element, note its classes, and either add those names to the safelist or write them in full somewhere the tool reads, then rebuild and retest.
6. Should I remove unused CSS or inline critical CSS first?
If your stylesheet is blocking the first paint, deal with that first by inlining the critical rules for each template and loading the rest without blocking. That changes what visitors wait for most directly. Removing unused CSS then shrinks what remains and makes both the inlined and deferred parts smaller. On a site where CSS is already loaded efficiently, trimming unused rules is still worthwhile housekeeping but will usually make less visible difference.
7. Can I remove unused CSS in WordPress without coding?
Yes. Several performance plugins include a remove unused CSS feature that builds a trimmed stylesheet for each page automatically. They save a lot of manual work, but test forms, menus, logged-in views, WooCommerce pages and anything that changes after the page loads, because those are where styles go missing. Clear the plugin's cache after design changes. Many sites also gain a lot simply by stopping plugins from loading their styles on pages where they are not used.
The CSS Checker estimates how much of your CSS goes unused and measures stylesheet size, alongside W3C validation, render-blocking stylesheets, specificity issues and prefix gaps.
Open CSS Checker Fixes โ