CSS Minifier & Beautifier

Global tools

Paste a stylesheet or a page and this tool gives you the smallest version of it that still does the same thing - or, in the other direction, the same file laid out one declaration per line so a human can read it. Every byte it removes is counted and named, so the result comes with its own audit: how many comments went, how many runs of whitespace, how many closing semicolons, how many `0px` lengths and how many colors were shortened.

Paste a whole stylesheet or a whole page and nothing is truncated. The page starts from a small sample sheet you can replace.

Minifying removes what is not rendered; beautifying changes only the layout, never the contents.

Auto reads the text. Choosing JavaScript on purpose takes you to the reasons it is refused.

Comments starting `/*!` are the license convention, so they stay unless you say otherwise.

Two rewrites only, both asserted: `0px` to `0` for lengths, and `#ffffff` to `#fff` where the pairs repeat.

Leave it blank and the name is built from what you pasted: "styles.min.css", "page.min.html".

Results update as you type. Nothing leaves your device.

Minified CSS

-39.5%

296 B became 179 B: 1 comment, 32 runs of whitespace and 3 closing semicolons went, and everything else is as it was written.

Input
296 B
Output
179 B
Saved
117 B
Comments out
1

Minified CSS

/*! danfio sample sheet */ .card{color:#fff;margin:0 auto;padding:10px 20px;width:calc(100% - 40px)}.card :hover{background:#eee}@media (min-width:700px){.card{padding:20px 40px}}

Copy it, or download it. The bytes in the box are the bytes in the file.

What changed, by name and by count

WhatHow manyNote
Comments removed1every /*! license banner kept
Runs of whitespace collapsed32never inside a string, a url(), pre, textarea, script or style
Semicolons dropped before a brace3a closing semicolon says nothing
Zero lengths shortened10px to 0, lengths only
Colors shortened2#ffffff to #fff, in values only

The counts are what this pass actually did rather than what it might have done, which is the whole reason for printing them.

What was deliberately not done

  • No rule is merged, no property is reordered and no vendor prefix is added: each of those can change which rule wins, and that is a decision for a build tool that has the whole project in front of it.
  • No value is rewritten except a zero length and a repeated hex pair. Turning `font-weight: bold` into `700` or shortening `margin: 4px 2px` needs the grammar of every property, which this pass deliberately does not have.
  • A colon in a selector is never tightened, because `a :hover` and `a:hover` are different selectors, and the spaces around `+` and `-` inside `calc()` are arithmetic rather than decoration.
  • A custom property holds whatever its author wrote, so `--x: 0px` stays as it is: the value is substituted somewhere else, where its type cannot be known from here.

A page is needed rather than a one-line regular expression because three characters in CSS and one in HTML carry meaning that whitespace and comments can hide. `a :hover` is not `a:hover`; `calc(100% - 20px)` needs its spaces while `margin: 1px -2px` needs its own; `--x: 0px` is a value this pass cannot type-check; and a `<pre>` block is whitespace a browser really does render. Each of those is handled by name here and named again in the list under the result. JavaScript is not minified at all, and the page says why rather than doing something plausible.

About this minifier & beautifier

Minifying a stylesheet or a page removes whitespace no browser was ever going to render, and that is all it removes. The page tightens CSS and HTML, keeps the spaces that are load-bearing inside `calc()` and between inline elements, and beautifies the same file back into something a person can read.

There is no account, no sign-in and nothing to install: a file is pasted or dropped and the result appears in the same tab. The minifier reads the stylesheet rule by rule rather than matching a regular expression, so a value it cannot classify - a string, a custom property, a `url()` path - is copied through untouched instead of guessed at.

The saving is real but modest, and the page never calls a byte count a performance budget: compression already collapses repeated spaces, so the gain is mostly in transfer and parse time. A file that does not balance is refused with its line and column. For a step that must not corrupt a file, that caution is the whole point.

How this minifier & beautifier works

What a minifier may delete without asking

Five things go, and only five. Comments, unless they start `/*!`, which is the convention for a license banner and is therefore kept by a switch that defaults to keeping it. Runs of whitespace are collapsed to one space and then dropped wherever a browser would not render them - next to a brace, a semicolon or a comma, at the start or end of the file, or with a block-level element on either side. A semicolon directly before a `}` is dropped, because the block was ending anyway. Inside a declaration value, `0px` becomes `0` for lengths only. And a six-digit color whose three pairs are identical becomes three digits.

Everything else is left exactly as written. No rule is merged, no property is reordered, no vendor prefix is added, `bold` is not turned into `700`, `margin: 4px 2px` is not shortened, and no color is converted to another notation. Those transformations need the grammar of every property and the cascade of the whole project, and a tool that only ever sees one file cannot have either. The output is deliberately of a kind you can diff against the input and understand.

The three places a careless minifier breaks a stylesheet

The first is a colon in a selector. `.card :hover` selects hovered descendants of `.card`; `.card:hover` selects the card itself. Both are valid, both are common, and one space is the entire difference - so this pass tightens a colon only inside a declaration block, where the property name has just ended. The same care applies to a media feature such as `(min-width: 700px)`, where either spelling is legal, so tightening that one is safe and is done.

The second is arithmetic. `calc(100% - 20px)` requires the spaces around the `-`, because `100% -20px` is a parse error rather than a subtraction. But `margin: 1px -2px` is two values, the second negative, and its space is just as load-bearing. The pass therefore keeps a space whenever a `+` or `-` is involved outside a function it has recognized as arithmetic (`calc`, `min`, `max`, `clamp` and their relatives), and preserves both sides inside one. Selectors are different again: `li + a` and `li+a` mean the same thing, so a sibling combinator really is tightened.

The third is a value the pass cannot interpret. A custom property holds whatever its author wrote - `--gap: 0px` may be substituted into a length, a time or a background list - so its value is copied through untouched, which also means a color or a zero inside it is left alone. Strings and `url(...)` are copied for the same reason: a `/*` inside a `content` string is not a comment, and `url(https://host/a/*b*/c.png)` is a real path. Only then can the counts above be trusted.

Which whitespace in HTML is actually rendered

HTML has one place where whitespace matters and one where it does not. Under the default `white-space: normal`, a run of whitespace collapses to a single space, and that space is kept when both of its neighbors are inline: two links on one line stay two links with a space between them. Beside a block-level element, at the start of a block or at the end of one, the whitespace is not rendered at all and goes - which is where almost all of the saving in a formatted page comes from, because that is all indentation.

Anything the pass cannot classify stays inline, which costs a byte and cannot change a layout. Four elements are never touched at all: `pre`, `textarea`, `script` and `style` are copied word for word, because their contents are text rather than markup - so a pasted page keeps its code block and its scripts exactly as they were. Comments go, except a license banner starting `<!--!` and a conditional comment `<!--[if ...]>`, which carries markup aimed at one browser. Tags are tidied from the inside: one space between attributes, no space around `=`, attribute values untouched, and the self-closing slash kept, because inside inline SVG that slash is what ends an element.

The honest limit is `white-space: pre-wrap`: an element styled that way does render its whitespace, and nothing in the text of a file says so. If the difference matters, wrap that content in `pre`, which this pass recognizes, or leave the page as it is.

Beautifying, and why the two directions round-trip

The other direction removes nothing at all. A stylesheet is laid out one declaration per line with indentation following the nesting, so an `@media` block reads as a block; a page is laid out one block element per line, with inline content left on the line it belongs to - `<p>Hello <b>world</b></p>` stays one line, because splitting inline content formats the source rather than the page. Comments are kept exactly where the author left them, on their own line if they had one. Attribute spacing is tidied the same way the minifier tidies it.

Because both directions use the same rule for deciding which whitespace exists, they compose: minifying a beautified file gives the same bytes as minifying the original, and beautifying twice gives the same result as beautifying once. Both properties are asserted rather than assumed. A file that does not balance - a `{` without its `}`, a string or a comment that never closes, a `<pre>` that is never closed - is refused with the line, the column and a caret under the trouble, rather than half-processed into something that looks finished.

Worked examples

Each example below was run through the calculator on this page when the site was built, so the numbers match what you see when you enter the same inputs.

The sample sheet, minified

Minified CSS

-39.7%

297 B became 179 B: 1 comment, 33 runs of whitespace and 3 closing semicolons went, and everything else is as it was written.

Input: 297 B · Output: 179 B · Saved: 118 B · Comments out: 1

The comment above `.card` goes and the license banner stays; `0px` becomes `0`, two colors lose their repeated pairs, the closing semicolons disappear, and the space in `calc(100% - 40px)` is untouched because it is arithmetic.

A banner kept, the note beside it removed

Paste your CSS or HTML: /*! (c) 2026 danfio - MIT */ /* spacing helper */ .card { margin: 0px auto; }

Minified CSS

-38.0%

79 B became 49 B: 1 comment, 6 runs of whitespace and 1 closing semicolon went, and everything else is as it was written.

Input: 79 B · Output: 49 B · Saved: 30 B · Comments out: 1

The `/*!` comment is the license convention, so it survives by default; the ordinary comment does not. Turn the banner switch off and the file starts with `.card`.

The same sheet, beautified

Direction: Beautify - lay it out for a human to read

Beautified CSS

19 lines

297 B became 297 B. Nothing was removed - every comment, value and selector is still there; only the layout changed.

Input: 297 B · Output: 297 B · Comments kept: 0 · Values changed: none

Nothing is removed in this direction: every comment, value and selector is where it was, and the only change is the layout.

The space that means two different selectors

Paste your CSS or HTML: .card :hover { color : red; }

Minified CSS

-25.8%

31 B became 23 B: 0 comments, 6 runs of whitespace and 1 closing semicolon went, and everything else is as it was written.

Input: 31 B · Output: 23 B · Saved: 8 B · Comments out: 0

The space before the colon is the difference between hovering the card and hovering something inside it, so it is kept - while the spaces around the declaration colon, which mean nothing, are tightened.

A script, refused by name

Paste your CSS or HTML: const add = (a, b) => a + b; console.log(add(1, 2));

JavaScript is not minified here

not implemented

This page minifies CSS and HTML. It will not touch JavaScript, and that is a decision rather than a missing feature.

Characters: 52 · Lines: 2 · Bytes: 52 B

Chosen automatically as JavaScript, and refused with the five reasons a scanner cannot do this safely. Paste the page the script lives in instead and the `<script>` is copied through word for word.

Frequently asked questions

Is my stylesheet or page uploaded anywhere?

No. The text is read, rewritten and counted by a fixed rule running in your browser tab, so there is no endpoint behind the page and the only request it makes is for the page itself. That is what makes it reasonable to paste a customer stylesheet or a private page here. The copy shown in the page’s static HTML was computed for the page from the sample sheet, so a visitor with JavaScript switched off still sees a real minification rather than an empty box.

Why will this not minify my JavaScript?

Because guessing there breaks code in ways that are hard to see. A line break can end a statement, so deleting it can join two statements into one that means something else. A `/` is division or the start of a regular expression depending on the token before it. A template literal can contain a string, a regex or another template. A `//` inside a URL string is not a comment, and `-->` at the start of a line behaves differently in strict and sloppy code. Every one of those needs a reader that understands code structure rather than a pattern scan, so the page refuses JavaScript by name and lists the five reasons. Use esbuild, terser, or the minifier your bundler already runs - and note that a `<script>` inside an HTML page you paste here is copied through word for word rather than touched.

Will minifying break my stylesheet?

The five removals - comments, unrendered whitespace, a closing semicolon, `0px` as a length, and a six-digit color with identical pairs - are all cases where the two spellings are interchangeable in the CSS specification, and each has been checked item by item before release as an actual behavior rather than only described in prose. What could still surprise you is upstream of the file: a step in your release pipeline that expects a comment as a marker (a `/* purge */` hint, an `@import` order comment), a tool that reads your source rather than the browser that runs it. If your pipeline greps the CSS, minify at the last step.

Does it merge rules, remove duplicate selectors or add vendor prefixes?

No, and none of those are oversights. Merging two rules with the same selector, or reordering declarations inside one, changes which declaration wins when specificity and `!important` are involved; removing a "duplicate" declaration is only safe if you know what each browser does with it; and prefixing needs to know which browsers you support, which is a question about your project rather than about your file. Those belong in a build tool that can see the whole project, and this page says so instead of trying.

Why is `0%`, `0deg` or `0s` left alone when `0px` becomes `0`?

Because `0px` and `0` are the same length and nothing else about them differs. A percentage is not a length - `width: 0%` resolves against the containing block - and an angle and a time are their own types entirely: `hue-rotate(0)` is not `hue-rotate(0deg)`, it is invalid, and `transition: 0` is not `transition: 0s`. The same reasoning is why a zero inside `calc()` is left as written: there the number is part of an expression, and while `calc(0 + 1px)` is legal, keeping the arithmetic untouched is easier to trust than to prove case by case.

What happens to `!important`, `@media` and `@supports`?

Nothing. `!important` is a token in the value and survives as it is, and at-rules keep their preludes, with the space that a media feature allows either way tightened. The one thing that is never touched in a prelude is the space before a colon, because in a selector that space is the difference between a pseudo-class and a descendant - and the pass cannot tell a selector from a media feature except by whether it is inside a declaration block, which is exactly the check it does use.

Can I use it the other way round, to read a minified file?

Yes, and that is the beautify direction. It removes nothing: the sheet comes back one declaration per line with indentation, comments where they were, and a page comes back one block element per line with inline content left on its own line. Attribute spacing is tidied the same way. The output is not a claim about the original author’s formatting - nobody can recover the line breaks someone else chose - but it is a file a human can read and a diff can work with.

Assumptions and sources

  • The cleanup follows the CSS syntax rules browsers use: comments, redundant whitespace, a semicolon before `}`, `0px` and repeated hex pairs.
  • Spaces that change meaning are kept, as in `calc(100% - 20px)` and `a :hover`; strings, `url(...)` and custom properties are left exactly as written.
  • An unbalanced stylesheet is refused with its line rather than half-processed, and everything runs in your browser with nothing uploaded.

Last reviewed 2026-09-17. Runs locally on your device; keep backups; no financial advice applies. Read the full disclaimer.

Where a figure on this page comes from an official agency — HUD, IRS, FICO or a state body — the site keeps a dated snapshot of the source page in its sources library, and a scheduled job re-checks that snapshot, so the number can be traced back to the document it came from.