You turn on “Remove unused CSS”, the report stops complaining about unused bytes, and the site looks fine on your screen. Then the reports come in. The mobile menu opens as a bare list. Icons have turned into empty squares. A whole section is blank on the home page. The product page is broken, but only for logged-in customers. Or everything was fine yesterday and one page broke overnight.
Switching the option off fixes it, and throws away one of the biggest gains on a heavy theme. This guide explains what the tool actually misses, how to read the symptom, and how to put back only the rules that are missing.
What a used-CSS pass does
The tool loads your page in a headless browser, at one screen size, logged out, without clicking anything. It records which CSS rules match an element at that moment, keeps those, and drops the rest. The page is then served with that trimmed stylesheet instead of the theme’s full files.
The weakness is built in: the snapshot is one state of one page on one device. Every rule that only matters in another state is judged useless and removed.
The six blind spots, and their signature
1. States that appear after a click. An open menu, a modal, a mini cart, the second tab of a tab block: their “open” class is added by JavaScript, so it was not on the page during the snapshot. Signature: the element opens unstyled, or does not appear at all. Two detailed cases: menus and overlays and the WooCommerce mini cart.
2. The other device. Rules inside a mobile media query only match when the render is done at mobile width. If the trimmed CSS was built at desktop width and served to phones, the mobile layout loses its rules. Signature: broken on one device only.
3. Content that arrives later or for someone else. Reviews loaded by a widget, related products loaded over AJAX, an infinite scroll, the admin bar, a filled cart, a notice for logged-in users. None of it existed for the logged-out visitor of the snapshot. Signature: fine for you in a private window, broken for a customer in a given state.
4. Icon fonts. An icon font is declared with @font-face and used by classes. If no kept rule uses that font family, the declaration is dropped with it, and any icon added later has nothing to draw with. Signature: empty squares or blank spaces where icons should be.
5. Entrance animations. Page builders often hide an element at load (opacity:0) and let a script add the class that animates it in. If the rule behind that class or its @keyframes was removed, the element stays invisible for good. Signature: a blank section that appears as soon as the optimization is off.
6. A render that went wrong. During generation, one stylesheet did not arrive: a timeout, a blocked request, or a builder that was regenerating its own CSS cache at that moment. The result looks almost right, with one component missing its styles, and it stays cached until the next generation. Signature: broken on some pages only, starting at a precise moment, and fixed by regenerating.
Step 1: confirm the used CSS is the cause
Load the broken page with the optimization bypassed (most tools offer a URL parameter or a per-page switch) or switched off, with every cache layer purged. If the page is fine, the trimmed CSS is the cause. If it is still broken, look elsewhere: a JavaScript delay produces very similar symptoms, and the JavaScript delay guide covers that case.
Then reproduce the bug in the state that breaks it: the right device width, the interaction (open the menu, hover the cart), the right visitor state (logged in, cart filled), and scroll to the broken area.
Step 2: find the rule that went missing
Open the unoptimized version in one tab and the optimized one in another. In both, right click the broken element, choose Inspect, and compare the Styles panel. The rules present on the left and absent on the right are the missing ones; the panel shows their selector and the file they came from.
To check a selector directly, paste this in the console of each version:
// Replace with the class you suspect (the open state, the icon, the animation).
const needle = '.is-open';
[...document.styleSheets]
.flatMap(s => { try { return [...s.cssRules]; } catch (e) { return []; } })
.filter(r => (r.selectorText || r.name || '').includes(needle.replace(/^[.#]/, '')))
.map(r => r.cssText.slice(0, 120));An empty result on the optimized page and a list on the original tells you exactly which rule to bring back. Stylesheets served from another domain may be hidden from this check; the Styles panel still shows them.
Step 3: put back what is missing, and only that
Match the fix to the blind spot:
- States after a click (1): add the state classes to the safelist, for example
is-open,active,show, or the component’s own prefix. When a component has many states, keeping its whole stylesheet out of the trimming is more reliable than chasing each selector. - Other device (2): make sure the tool generates for mobile and desktop, and regenerate after changing it.
- Late content (3): safelist the widget’s prefix, or keep the stylesheet of that plugin whole.
- Icon fonts (4): safelist the icon classes (often a prefix such as
fa-oret-icon), which keeps the font declaration with them. - Animations (5): safelist the animation classes of your builder, or turn off entrance animations above the fold, which also helps the Largest Contentful Paint.
- Bad generation (6): purge the builder’s own CSS cache first, then regenerate the trimmed CSS of the affected pages. Regenerating on top of a half built builder cache reproduces the same hole.
Narrow fix
A few state classes or one component stylesheet, kept on the templates that need them. The rest of the site stays trimmed.
Broad fix
The option switched off, or every stylesheet of the theme excluded. The bug is gone, and so is the gain.
After the fix: test the states, not just the page
- Mobile and desktop, each with the menu, the search and the cart opened.
- Logged in and logged out, empty cart and filled cart.
- Scroll the whole page: late sections and animated blocks are where holes hide.
- After a theme, builder or plugin update, check again: new class names need to be safelisted again.
- And, as always, one risky optimization at a time, as in our method for optimizing without breaking.
With Mantys Core
Mantys Core can run your whole performance layer, cache included, or sit next to the setup you already have. Its used CSS is built to limit these blind spots and to fail safe when a generation goes wrong:
The used CSS is generated page by page, and its settings (safelist, stylesheets kept whole, loading mode) can be set per template, so the shop and the blog do not share one compromise.
The critical CSS of each page is built from separate mobile and desktop renders, at the sizes the speed test uses.
A stylesheet you choose to keep whole is left untouched, and inline styles stay in place.
An abnormally small result is refused and the last good version keeps being served, instead of stripping the page.
When a theme or plugin changes its CSS, the pages are regenerated in the background while the previous version keeps serving, and saving a post regenerates that page.
A one-request bypass shows the raw page next to the optimized one, which is the comparison of step 2.
You still decide which states matter. The platform keeps one exception from turning into a site-wide compromise.
In summary
- A used-CSS pass keeps what matched during one render: one state, one size, logged out, no interaction.
- Six blind spots: states after a click, the other device, late content, icon fonts, entrance animations, and a generation that went wrong.
- Confirm with the optimization bypassed, then compare the Styles panel of both versions to name the missing rule.
- Put back only that rule, by safelist or by keeping one stylesheet whole, on the templates that need it.
- Test the states, both devices and both visitor types, and recheck after every update.
Comments
Stuck on a similar problem? Describe your setup and what you see. We read every comment and answer.
No comments yet. Be the first to share your case.