DevKitHub

Programming

CSS Minifier and Beautifier

Paste a stylesheet to minify it, or switch to Beautify to lay it out with one declaration per line and nested blocks indented. Nothing is reordered or merged, and every result is re-tokenised before it is shown, to prove no two tokens were run together.

27 lines
1 line

Size

SavedUTF-8
386 → 285 bytes, 26.2% smaller

Nothing is reordered or merged. Strings, url() and custom property values are left exactly as written, and zeros keep their units inside calc() and in flex.

This tool runs entirely in your browser. Your input is never uploaded, stored or logged.

How it works

The stylesheet is tokenised by the rules of CSS Syntax Level 3 before anything is removed, because whether whitespace matters in CSS depends on where it is. In .nav .item the space is the descendant combinator, and .nav.item matches something else; in calc(100% - 10px) the spaces are required, and the browser drops calc(100%-10px); in @media screen and (min-width: 600px) the space stops "and(" being read as a function. Minifiers built on regular expressions get these wrong, and nothing looks broken until the layout is. Here strings and url() are single tokens that are never touched, nested rules are told apart from declarations as CSS Nesting specifies, and whitespace is removed only where the grammar gives it no meaning: around commas, brackets and braces, the colon of a declaration, the > + and ~ combinators, slashes, !important, and the operators inside media query parentheses.

Minifying removes comments except /*! licence comments (a switch removes those too), the last semicolon in each block and rules with nothing in them, but not an empty @layer, which still sets the layer order, or an empty @keyframes, which still replaces an earlier one. Leading zeros go, so 0.5em becomes .5em, and trailing ones only while the number stays a non-integer: z-index: 1.0 is invalid and z-index: 1 is not, so 1.0 is kept. Hex colours are lowercased, and shortened only when exact: #AABBCC becomes #abc, #AABBCD stays. A zero length loses its unit only in properties that take nothing but lengths there: never inside calc(), min(), max() or clamp(), where a bare 0 is a number and a type error, and never in flex, flex-basis or line-height, where 0 means something else. Custom property values, the spacing inside an An+B expression such as :nth-child(2n + 1), and IE hacks such as *zoom are kept as written.

Before any output is shown it is tokenised again and compared with the tokens that were meant to be written. Where removing a comment would join two tokens into one, a separator goes back: a space where the source had one, otherwise an empty /**/, because an invented space could be a descendant combinator. Beautifying changes whitespace only: every comment stays, and no value is rewritten. Problems a browser recovers from silently are named instead: a } with no matching {, a block left open at the end, an @import after other rules. It is CSS only: Sass, SCSS and Less syntax, such as // comments and nesting with $variables, is not understood. Stylesheets over 100,000 characters are processed in a background worker so typing stays responsive.

Common problems

Every example below is run against this tool in our test suite, so what it says here is what the tool actually does.

An unquoted url() cannot contain spaces, quotes or brackets. Put the address in quotes: url("…").

.hero { background: url(images/hero banner.png) }
Why:
In url() without quotes, a space ends the address. The tokeniser reads the rest as a bad URL, and the browser drops the whole declaration, so the image simply never loads and nothing reports why.
Fix:
Quote the address, as url("images/hero banner.png"), or encode the space as %20.

A comment opened with /* is never closed, so a browser ignores everything after it.

.a { color: red; } /* TODO: tidy
.b { color: blue; }
Why:
CSS comments do not nest and do not end at a line break, so an unclosed one hides every rule after it. It usually happens when a block that already contained a comment is commented out.
Fix:
Close it with */. To comment out code that contains comments, close or remove the inner ones first.

A string opened with ' is never closed.

.quote::before { content: 'It's here'; }
Why:
The apostrophe in It's ends the single-quoted string, and the quote after here opens a new one that never closes. Everything from there to the end of the line, or the file, is swallowed into it.
Fix:
Escape it as \' or use the other quotes: content: "It's here".

The layout broke after another minifier removed the spaces in calc().

Why:
Inside calc() a + or - needs whitespace on both sides. calc(100%-10px) is not valid, so the browser drops the whole declaration. Minifiers that strip the spaces around every operator produce exactly that.
Fix:
Nothing to do here: the spaces around + and - in calc(), min(), max() and clamp() are always kept.

margin: calc(0px + 1em) stopped working after minifying elsewhere.

Why:
Shortening 0px to 0 is safe in margin: 0, but inside calc() a bare 0 is a <number>, and adding a number to a length is a type error, so the declaration is dropped.
Fix:
Nothing to do here: zeros keep their units inside math functions, in flex and flex-basis, and wherever a bare 0 would mean something else.

A // comment made the declaration after it disappear.

Why:
CSS has no line comments. // is two delimiters, so "// note" and everything up to the next semicolon, including the declaration on the following line, is one invalid declaration that the browser drops.
Fix:
Write comments as /* note */. Sass and Less accept //; plain CSS does not. The minifier keeps the text exactly as a browser reads it rather than guessing.

Frequently asked questions

Can minifying change how my page looks?
It should not. The changes are limited to whitespace, comments, the spelling of numbers and hex colours, units on zero lengths, final semicolons and empty rules, and the test suite checks that minified stylesheets keep every other token of the input. Each result is also re-tokenised before it is shown, so two tokens are never run together. Nothing is reordered or merged, because moving a rule can change which one wins the cascade.
Why is 0px not always shortened to 0?
Because a bare 0 is not always a length. Inside calc() it is a number, and calc(0 + 1em) is invalid. In flex, 0 sets flex-grow while 0px sets flex-basis. In line-height, 0 is a multiplier. And where a property takes only numbers, opacity: 0px is dropped by the browser while opacity: 0 applies. So the unit is removed only in properties such as margin, padding, width and border that take lengths there.
Does it keep licence comments?
Yes. A comment that starts with /*!, the usual marker for a licence header, is kept by default, and every other comment is removed. Turn off "Keep /*! licence comments" to remove those as well. Beautifying keeps every comment.
Does it work with Sass, SCSS or Less?
No. It reads CSS as a browser does, including native nesting with &, @layer, @container and @scope. Preprocessor syntax such as // comments, $variables and mixins is not CSS, and a browser would drop most of it, so compile to CSS first and minify the result.
Is my stylesheet uploaded?
No. Minifying and beautifying run entirely in your browser; large stylesheets are handled in a background worker on your own machine, not on a server.

Last updated