CSSTidy CSS Formatter

CSS Minification Best Practices: What Survives the Build Step

CSSTidy — CSS Formatter & Minifier Guides · Updated 2026-10-12 · All guides

Minification has one rule that outranks every other rule: the transformation must not change what the browser sees inside a string. Everything else — whitespace, shortening colours, dropping the last semicolon — is cosmetic and reversible. Strings are not.

The failure you should assume is happening

.a::before { content: "x/*y*/z" }
.b::after  { content: "}" }
.c::before { content: "12:30" }
.d         { background: url("a;b.png") }

A textual minifier that strips /* … */ before it knows where strings start turns line 1 into content:"xz". A textual beautifier that inserts newlines at every } cuts line 2's block in half. Line 3 is the one that fools people: it survives in most tools, which builds false confidence in the tool right up until line 1 costs you an afternoon.

This site's own tool has both failures, and they are reproduced against the shipped code — with the actual outputs — on About. The minifier survives the quoted }; the tidier does not. The minifier does not survive the quoted comment. Reading your own tool's limits in your own words is cheaper than discovering them in a production bundle.

The general rule: if a tool cannot tell a string from a comment, it cannot be trusted with either.

The order of transforms matters more than the transforms

Every minifier is a sequence of steps, and the order is where the bugs live:

  1. Tokenise (or don't — everything below is unsafe if you skipped this).
  2. Strip comments.
  3. Remove whitespace that is not semantically required.
  4. Collapse values (#ffffff → #fff, 0.5px → .5px, drop trailing ;).
  5. Merge/shrink rules.

Steps 2 and 3 are the dangerous pair. Doing them as regex over raw text is the entire bug class in the section above. If you are writing your own minifier — for a build step, a static site, an in-browser tool like this one — that is the order to implement, and step 1 is not optional if the input is anything users paste in.

What is safe to assert in CI

Fuzz the boundary instead of trusting the tool. Cheap version, no dependencies:

# Run the real tool over inputs chosen to break textual transforms,
# then diff. Any output where a string literal changed length = a real bug.
for probe in \
  'a::before{content:"x/*y*/z"}' \
  'a::after{content:"}"}' \
  'a{background:url("a;b.png")}' \
  'a{content:"12:30"}'
do
  out=$(my-minifier <<<"$probe")
  [ "$out" = "$probe" ] || { echo "MANGLED: $probe -> $out"; exit 1; }
done

Those four lines are the whole test suite, and they take under a second. Most minifiers that fail them fail all of them, and most teams find out during a release.

Two more assertions worth having when you depend on someone else's minifier:

  • Byte-budget check on a known input. Pin one CSS file, record its minified hash, fail if the hash moves after a toolchain upgrade. Minifier upgrades change output; that is fine, silent is not.
  • Round-trip readability check. Unminify the output and compare declarations. You will not get byte-identical input back, and you should not expect to — but a declaration that vanished is a different thing from a reformatted one.

Rules that survive review

  • Never minify a file you have not looked at once. A hand-maintained stylesheet with a content: "}" in it is not rare, and minification is where the bug becomes invisible.
  • Keep your source readable; minify only in the build artifact. If you are reading minified CSS to debug, your pipeline is missing a sourcemap, not a cleverer minifier.
  • Let the minifier drop the last semicolon, not your formatter add the first two. Rules like "always one declaration per line" belong in a linter with a fix mode; encoding them as blind text substitution is how a formatter starts losing data.
  • Pin the minifier's version. +56% download growth on a package last published 2023-11-30 (clean-css, registry pull 2026-10-12) is evidence that people inherit these tools rather than review them. Your minifier is a dependency with a maintainer; check they still exist.
  • Comments that must survive need a marker. If a banner/license comment has to ship, the tool needs an explicit rule to keep it (/*! … */ by convention in most PostCSS-era tools). "It kept it last time" is not a rule.

Where this site fits

csstidy.net is textual on purpose: no install, no upload, nothing leaving the browser, works offline on a plane. Those are real benefits for the 20 KB stylesheet you are inspecting, and they are bought with the exact limitations above. It is the wrong tool for a production bundle, and the alternatives page is the honest ranking of things that parse.

Developer Sponsor / Partner
Copied to clipboard!