Free online CSS formatter and beautifier
CSS formatter that leaves the cascade alone
Paste a minified or squashed stylesheet and get every declaration on its own line, with @media, @supports, @layer and native & nesting indented to whatever depth the file actually reaches. Free, no signup, nothing uploaded. Rule order is untouched, because in CSS order is the cascade — nothing is sorted, merged or deduplicated, and no value is shortened. 25.2 KB of shipped Tailwind expands to 1,686 lines in 4 ms; the limit is 5 MB.
- 100% free
- No signup
- Up to 5 MB
- Order never changed
- Nesting indented by depth
One declaration per line, appearing here.
Paste CSS, drop a file, or press Ctrl+V anywhere on this page.
How to format CSS
Paste the sheet, choose the shape, read the tally.
Bring the stylesheet
Paste the sheet in, hit the ⌘V or Ctrl+V shortcut from wherever your cursor happens to be, or open a .css file off disk. The usual input is machine output: a minified bundle from a CDN, a build artefact you are auditing, or a rule you copied straight out of devtools. A fragment works as well as a whole sheet.
Pick the shape you read best
Set the indent unit — two spaces, four, or a tab. “One selector per line” gives a long selector list one line each, which makes a diff readable; turn it off to keep the list on a single line. “Blank line between rules” adds breathing room between rules and at-blocks, and nothing else about the sheet changes either way.
Read the tally and take the file
Underneath you get rules, declarations, at-rules, comments kept and the deepest nesting level reached — a real Tailwind build reports 287 rules, 610 declarations, 65 at-rules and a depth of 4. Copy takes the expanded sheet; Download saves it as .css.
Technical specifications
| Layout | One declaration per line, one space after the colon, blocks indented by 2 spaces, 4 spaces or a tab; optional one-selector-per-line and optional blank line between rules |
|---|---|
| Selectors | The >, + and ~ combinators get a space either side; bracket depth is tracked so the + in :nth-child(2n+1) and the ~= in [data-x~="y z"] stay welded. Case, order and specificity are untouched |
| At-rules | @media, @supports, @layer, @container, @keyframes, @font-face and native nesting each indent their contents; colons are spaced only inside a prelude's parentheses, so @page :first survives; @charset, @import and statement @layer stay on one line |
| Values | Collapsed to single spaces with one space after each comma — except inside strings, comments and unquoted url() bodies, where a comma is content and is left alone. Custom property values are copied out verbatim |
| Comments | Every /* … */ comment is kept, above the declaration or at the end of its line, exactly where you put it |
| Never done | No rule is reordered, merged or deduplicated, no property is sorted, no shorthand is expanded or collapsed, no value is shortened, no vendor prefix is added or removed. Formatting is not a build step |
| Not supported | // line comments from SCSS or Less are read as part of the next selector; nothing is validated, so an unknown property or a typo in a value is laid out rather than flagged |
| Throughput | 25.2 KB of shipped Tailwind CSS — 287 rules, 610 declarations, 65 at-rules, nesting four deep — expands to 1,686 lines in 4 ms; 43.8 KB of hand-written CSS takes 3 ms. 5 MB per sheet, all of it in your browser |
Frequently asked questions
Can formatting change how my styles apply?
It cannot, because nothing is reordered and nothing is combined. A stylesheet is an ordered list where the last equally specific declaration wins, so a tool that sorts your properties alphabetically or merges two rules with the same selector is editing the cascade even when the output looks tidier. Here the rules come out in the order they went in, duplicates stay duplicated, shorthand and longhand keep their relative positions, and no selector is rewritten — the only characters that move are whitespace, plus the line breaks that put each declaration on its own line.
How are @media, @supports, @layer and @container laid out?
Each one opens a block that indents its contents one level, to whatever depth the sheet actually goes — a real build here nests four deep without complaint. The prelude gets a space after the colon inside its parentheses, so (min-width:48rem) becomes (min-width: 48rem), and that spacing is applied only inside parentheses on purpose: @page :first has a colon that starts a pseudo-class and must not be touched. Statement at-rules that end in a semicolon rather than a block — @charset, @import, @layer base, components — stay on one line.
Does it understand native CSS nesting and the & selector?
Yes: a nested rule is a rule, and it is indented under its parent like any other block. That is now the common case rather than an exotic one, since nesting shipped in every major browser and Tailwind-style builds emit it freely. Ampersand selectors, nested at-rules inside a rule, and rules nested inside those are all laid out by depth. The depth counter under the tool tells you how far down the sheet goes, which is a useful smell test on a file you have inherited.
Can I paste SCSS, Less or PostCSS?
Mostly, with one specific gap: // comments. Variables like $brand, at-rules like @mixin and @include, and nesting all survive perfectly well, because they fit the same block-and-declaration shape the reader already follows. But a double-slash comment is not CSS syntax at all, and the reader treats the rest of that line as part of whatever comes next — so a // note above a selector ends up glued to it. Convert those to /* … */ first, or expect to tidy them afterwards.
Why is my custom property left exactly as I typed it?
Because a custom property does not hold a CSS value, it holds an arbitrary run of tokens that something else will substitute later. --gutter: 0px 16px might be read by a calc() somewhere, pulled apart by JavaScript, or fed to a property that needs the unit spelled out; --shadow: 0 0 0 1px red is not the tool's business to re-space. Everything after the colon is copied out unchanged. Ordinary declarations do get normalised: one space after the colon, one space after a comma, and the value collapsed to single spaces.
What happens to comments and to strings full of punctuation?
Comments are all kept, and where they sit is kept too: one written above a declaration stays above it, one written after it stays on that line's end. Strings are the more interesting case, because CSS values can contain characters that look structural. content: "} not a brace" does not end the rule, a font stack like "Foo, Bar", sans-serif keeps the comma inside the quoted name, and an unquoted url() holding a base64 data URI is copied out whole rather than having its commas and semicolons respaced.
What makes it refuse to format a sheet?
Five things, and each one names itself: a string that never closes, a /* comment that never closes, a url() that never closes, a } with no matching {, and a file that ends inside an open block. In all five the original text comes back untouched with the reason underneath. That is deliberate — once the brace nesting is ambiguous there is no honest way to say which rule a declaration belongs to, and guessing would move a property into the wrong selector, which is far worse than a stylesheet that stayed ugly.
About expanding a stylesheet
Almost every stylesheet worth pasting into a formatter is machine output. It is a production bundle you are trying to work out the origin of a rule in, a block copied out of devtools, or a framework build where 25 KB of utilities arrive on four lines. What you want from it is not beauty, it is the ability to point at a declaration and say which selector owns it and which media query it is inside. That is why the two knobs here are the two that decide readability — the indent unit, and whether a long selector list gets a line each — and why everything else is left the way the file had it.
The reason to be conservative is that CSS carries its meaning in the order of things. Two rules with equal specificity are resolved by which came last, so sorting properties or merging duplicate selectors quietly rewrites which declaration wins; the same is true of anything inside @layer, where the declaration order of the layers themselves is the whole point. The other trap is the declaration value, which is not a tidy list of words: it can hold a quoted font name with a comma inside it, a base64 data URI whose payload contains semicolons and commas, or a content: "}" that will end the rule early for anything counting braces naively. Strings, comments and unquoted url() bodies are therefore copied across as single units rather than respaced.
One thing an expanded sheet is not is a deliverable. Indentation is bytes, and those bytes are on the critical path for rendering, so read in this shape and ship the other one: the CSS minifier puts it back on one line and tells you what that cost in bytes and in gzip. If the sheet you are reading arrived inside a page rather than as a file, the HTML formatter will pull the same printer through every <style> block it finds.
Where your stylesheet is processed
Parsing and printing both happen in this tab, so the sheet is never uploaded, and nothing in it is fetched: a url() pointing at a font or a background image stays a piece of text. Close the tab and the file is gone with it.