There is a particular kind of cowardice in framework work: you add a class because someone might want it, and you never remove it because someone might be using it. Do that for a year and you have seventeen hundred classes, a quarter of which have never been typed by a human.
We spent three sessions deleting. The framework went from 1,740 classes to 857 and the bundle from 20.6 KB to 18.3 KB gzipped. Not one component was lost.
How we decided, rather than how we felt
Every cut started with a count: how many classes does this family carry, and how many of them does the site — 71 pages of it, including 32 example pages built to exercise the framework — ever use? That is not proof a consumer isn't using something, but it is a much better argument than taste.
The breakpoint tiers made the case themselves. We shipped five: 576, 768, 992, 1200 and 1400. Across every page we had written, the 576 tier was used zero times, 1200 zero, 1400 zero. The reason is structural rather than accidental — the site caps its measure at 987px, so a tier that only applies above 1200px can never fire. Two tiers left, 180 classes gone.
What gap did to the margin utilities
The spacing utilities were the biggest single family: margin and padding for every side and axis, at every step, at every breakpoint. 194 rules.
They are a holdover. Before gap worked in flex — which is to say, before about
2021 — the only way to space a row of things was a margin on each child and a negative margin on
the parent to cancel the last one. That whole technique is gone. A stack with a gap does the same
job without the cancellation, and the layout owns the spacing rather than each child carrying its
own opinion about it.
<!-- then -->
<div class="row">
<div class="col mb-4">…</div>
<div class="col mb-4">…</div>
</div>
<!-- now -->
<div class="pf-stack pf-gap-4">…</div>
We kept mt, mb, p and the auto margins, because a
one-off nudge is real and margin-inline: auto centres things. Everything else went.
The variant maths
The optional variants file generated 26 utilities across 9 states: 200-odd classes from two lists. It is worth staring at that multiplication, because it is how these files get big without anyone deciding they should.
Two states came off — the scheme ones, which only ever did what a one-line rule does, since the role tokens already flip with the scheme. Then twelve utilities came off: a second accent, an accent wash, three opacity steps, an upward scale. Nobody varies opacity on hover. The remaining 14 × 7 is 99 classes covering colour, border, underline, elevation, ring and the two press transforms — the vocabulary of interaction, and nothing else.
The twelve-column grid
This was the one that felt like a decision rather than a tidy-up. 114 classes: twelve spans across three tiers, offsets, order, gutters.
Everything it did is covered by layouts that carry their own breakpoints and know what to do
when space runs out — which a column span cannot express. col-md-6 says "half a
container at 768px and above"; it cannot say "two columns, unless a column would be too narrow to
read". The grid went, the replacement table went into the docs, and the migration guide maps every
Bootstrap column class onto something.
The bug we found by deleting
Here is the part I did not expect. When the grid module came out, I went looking for it in the release script — and it was not there. The script has a hand-written list of modules to minify and bundle, and the grid had never been in it.
Which means the twelve-column demo on our own components page, on a page that loads the bundle, had been rendering with no grid CSS at all. For releases. Every check passed: the CSS was valid, the classes existed, the page validated, the class reference documented them. No check asked whether the file was in the bundle.
The fix is five lines: the release script now compares its list against the module files on disk and refuses to run if one is missing. That is the rule we keep relearning — when a bug gets past you, the useful output is not the fix, it is the check.
| What went | Classes | What does the job now |
|---|---|---|
| Breakpoint tiers sm, xl, xxl | 180 | md and lg, or a media query of your own |
| Per-side margin and padding | 194 | gap on the layout that owns the spacing |
| Twelve-column grid | 114 | .pf-grid--2/3/4 and the layout primitives |
| Offsets | 36 | .pf-ms-auto |
| Scheme variants | 18 | Role tokens, which already flip with the scheme |
| Twelve variant utilities | 84 | Nothing — nobody varied opacity on hover |
What deleting cost
Honesty about the trade. Someone who knew col-md-6 now has to learn
.pf-grid--2, and a vocabulary that transfers between projects has real value. Someone
using pf-px-4 has to change it. Pre-1.0 that is our prerogative, and every rename is
aliased in the compatibility file until 1.0 — but a removal is not a rename, and I would rather say
that plainly than bury it in a changelog entry.
What you get back: a framework where the reference is short enough to read, where there is one obvious way to space a stack, and where a class you find in the markup is a class somebody chose deliberately.
What this post does not cover
- How to migrate markup that used the removed classes — the migration guide maps each one.
- Anything about the optional modules: Fonts, Compat and Classless were untouched by this pass.
- A defence of utility-first CSS in general. This is what one framework removed, not an argument about yours.
Checked against gap, breakpoint tiers, layouts that carry their own breakpoints. Code blocks are reproduced from the PolyFishCSS source files, not reconstructed from description. Figures are measured from the build they describe.
What this doesn’t cover
A removal is not a rename, and the compatibility file cannot alias a media query or a column system. If you were using the tiers or the per-side spacing utilities, this release costs you an afternoon. What we kept is a judgement based on our own usage across 73 pages; yours may differ, and the classes are three lines each to add back.
Sources: the changelog and the class reference, which is generated from the CSS on every build. The before-and-after markup is illustrative, written for this post; the class counts, byte sizes and usage numbers are measured from the build rather than estimated.