# What unused CSS actually costs a small site

> A coverage audit calls about a third of this stylesheet unused. Across every page, 3.9% of it is. The gap is cache, and the real waste was in the head.

Source: https://plantroomlabs.com/notes/unused-css-is-other-pages-css/  
Published: 2026-10-05 (5 October 2026) · Plantroom Labs  
Topics: CSS, Performance, Static hosting

An audit measures one page. The browser downloads for a site. On this one the two answers differ by a factor of 8 &mdash; and the bigger number was not the stylesheet at all.

## The number a per-page audit gives you

Point a coverage tool at a page on this site and it reports about a third of the stylesheet as unused. That is not the tool being wrong. Measured across every document that loads it, the median share of bytes inside rule blocks that match nothing on the requesting page is **32.7%** — of 38,090 bytes of declarations, in 308 rules declaring 93 class selectors, loaded by 100 of the site's 105 HTML documents.

The spread is wider than the median suggests. The best page is `/` at 23.0%, because it has the most markup on it. The worst is not a page anybody reads: one of 3 redirect stubs, `/building-automation/`, at 72.2% — 967 bytes of HTML that fetch the whole stylesheet to print one sentence before leaving. Among the documents that are not stubs the highest is `/404.html` at 47.9%.

So the number is real, and it is a statement about *one page*. The browser does not download for one page.

## The same file, measured across every page

Ask instead which rules match nothing on *any* of the 100 documents and the answer is 1,502 bytes — **3.9%**. Everything between that and the per-page figure is other pages' CSS: paid for once, served from cache afterwards, and used by the second page the visitor opens.

That is the number that decides whether to split a stylesheet per page, and on a site this size it decides against. The file is 19,129 bytes compressed. Splitting it would save a fraction of that on a first page view, lose the cache hit on every view after it, and buy a build that can ship a page missing a rule it needs.

> **Two different questions.** “What does this page not use” is an audit. “What does this site not use” is a deletion list. Only the second is safe to act on, and on this stylesheet they differ by a factor of 8.

## What was actually dead

The 1,502 bytes matching nothing anywhere are 12 rules, and they are not one category. Of them, 1,058 bytes in 9 rules are reachable and have to stay. They are applied by a script, so the class never appears in anything the build writes — and one of them is why a text search is not good enough. The [EDE checker](https://plantroomlabs.com/tools/ede-check/) sets a verdict chip with `"pl-chip pl-chip--" + kind`, so `pl-chip--bad` and `pl-chip--warn` exist nowhere on this site as whole words. They are applied when a visitor's own file has errors in it. A remover that searched the HTML, or even the HTML and the scripts, for whole class names would have deleted the styling for the one state that matters.

Another 444 bytes in 3 rules match nothing and are kept on purpose: they are three of the four kinds of a theme the site does use, and a grouped rule further down the file names all four. Deleting them would remove a capability rather than waste. That is a decision, so it is written down as one, with its reason, where the gate can read it.

What was left was 1,046 bytes in 10 rule instances — a card component the home page stopped using, plus two orphans — checked against every HTML file, every script and the generator, and then deleted.

The rule that came out of it, and that now fails this build rather than living in somebody's memory: a rule matching nothing has to be reachable by a script, or named with a reason. Silence is what fails.

## The mistake that hid eight rules

The first version of this measurement called a rule used if any class in its selector appeared on a page. `.pl-pillar .pl-chip` passed, because `pl-chip` is on nearly every page — and that selector cannot match a document with no pillar in it, however many chips it has. A selector needs *every* class in one of its comma-separated groups, and a rule is out of reach only when every group has one missing. Fixing that found eight more rules and moved every figure above.

The second mistake was the set of documents. The measurement walked every HTML file in the build, 5 of which load no stylesheet at all — the demo frames, an OG card and a brand preview. One came out as the worst page on the site, which is a figure about a stylesheet it never requests; worse, their class names were quietly protecting rules from being called dead. Both errors pointed the same way: an audit slightly wrong about its scope is wrong in the direction that hides waste.

## The bigger number was in the head

While measuring the stylesheet it became obvious it is not the thing to measure. A first visit here is 104,992 bytes of font — 64,388 for the body face and 40,604 for the mono one — against 19,129 bytes of compressed CSS. The two faces are 5.5 times the stylesheet, and fonts arrive already compressed, so that is the whole figure. The [whole-site measurement](https://plantroomlabs.com/how-this-site-is-built/) published here in October puts them at most of a page load, in its own transcript.

What a whole-site figure cannot see is which documents pay for what. The mono face is drawn on 88 of the 100 documents that load the stylesheet. Before this pass all 100 of them preloaded it, so 12 fetched 40,604 bytes at high priority and never drew a glyph with them. The demo frames were worse: they preloaded *both* faces, 104,992 bytes, while loading no stylesheet that declares either `@font-face` — so the browser could not have used the bytes if it had wanted to.

Both are derived now rather than written into the template. Each document's preloads come from the stylesheets that document actually loads: the faces from their `@font-face` rules, and the markup that draws one from the rules reaching its family, directly or through a custom property. A page earns a preload by having markup that can match such a rule. 85 documents preload the mono face today, and adding a mono rule to the stylesheet changes which ones on the next build.

A preload is a promise that a file is needed before the layout has asked for it, and the browser keeps that promise at the expense of everything else on the page. Check the head before auditing the stylesheet.

## What a byte count on the wire is worth

One figure here is not reproducible, and it is the one most performance budgets are written in. Five requests for the same stylesheet — same compression, same cache status, same age — came back as 19,784 / 19,798 / 19,851 / 19,798 / 19,875 bytes. A later burst the same day returned 19,804 five times. Nothing about the file changed; the edge compresses per machine, and a burst lands on one machine.

So the sizes published above are the ones anybody can reproduce: the file on disk at 66,753 bytes, and `gzip -9` of it at 19,129. The variation is small. The part that matters is that a budget pinned to a wire byte count is measuring somebody else's compressor on the day it ran.

## Doing this on your own site

Four things, in the order they pay:

- Measure the head before the stylesheet. Fonts, preloads and anything else with `rel="preload"` on it are usually the larger number and always the earlier one.
- Measure per page *and* across every page. The gap between the two is cache, not waste, and acting on the per-page figure is how a site ends up shipping a page without a rule it needs.
- Before deleting a rule, search the scripts — including for class names a script builds by concatenation, which appear nowhere whole.
- Sort what is left into three: reachable at runtime, kept for a named reason, and deletable. Then gate it, so the next rule that matches nothing is a build failure rather than a line nobody reads.
All of the above is this site measuring itself, which is the only site we are entitled to publish numbers about. The scripts behind it are the same kind of thing we build for [a site that has to stay measurable](https://plantroomlabs.com/services/website-design/) and for [a pipeline that gates its own claims](https://plantroomlabs.com/services/web-and-devops/).
