Convert CSS to SCSS without inventing structure — the same rules in consistent SCSS layout, plus one opt-in transformation: repeated colour and length literals extracted into $variables with every extraction counted and listed, and the output verified declaration-by-declaration against the input before you see it. Free, no sign-up — runs locally in your browser.
Why convert CSS to SCSS at all?
Usually as the first step of a migration: a project adopts a
Sass build pipeline — for its variables, its tooling, or a
framework that ships SCSS — and the existing stylesheet needs
to cross over without changing what it renders. Since CSS is a
subset of SCSS, the crossing itself is legal the moment the
file is renamed; what a converter adds is the cleanup that
makes the file worth editing afterwards: consistent layout
after years of mixed authorship, and — with the opt-in switched
on — the repeated brand colours and spacing lengths gathered
into named variables, which is the first real SCSS feature
most stylesheets want. What it must never add is invention:
structure the author did not write, in a file whose entire job
is to be identical.
How to use this tool
- Paste the CSS, upload a
.css file, or drop one on the panel. The structure is checked first — a missing brace is reported with its line and column, not converted. - Choose an indent — 2 spaces is the SCSS community default.
- Decide on variable extraction. Leave it off for a pure layout pass; switch it on (and set the threshold) when you want the repeated literals named. Every extraction will be listed above the result.
- Press Convert to SCSS. The output is verified — re-parsed, variables substituted back, compared declaration by declaration — before it is shown.
- Copy or download as
.scss, then run it through your Sass compiler: it should emit your original CSS, plus nothing.
What the conversion does — and what it does not
Does: reprint every rule, at-rule and comment
in one consistent layout; keep declaration order and values
exactly; nest @media/@supports/@keyframes content by
indentation (the structure was already there); optionally
extract repeated colour/length literals into variables, listed
to the last occurrence. Does not: infer
selector nesting from shared prefixes; merge or reorder rules;
rewrite values (no 0px → 0, no colour
shortening); touch strings, comments or url()
bodies; or extract from custom property definitions. A
conversion you can diff is a conversion you can trust — this
one is built to diff cleanly.
Worked example
Given this CSS, with extraction switched on at the default threshold of 3:
.card{background:#1a73e8;color:#fff}.card a{color:#1a73e8}.btn{background:#1a73e8;border:0}
the converter emits:
$color-1: #1a73e8;
.card {
background: $color-1;
color: #fff;
}
.card a {
color: $color-1;
}
.btn {
background: $color-1;
border: 0;
}
and lists the single extraction above the result:
$color-1 = #1a73e8, 3 occurrences. The white
#fff appears once, so it stays literal — the
threshold is the difference between a variable and clutter.
Frequently asked questions
If CSS is already valid SCSS, what does this converter actually do?
Three things, and only three. It reprints the stylesheet in consistent SCSS layout — one declaration per line, nested blocks inside @media and @keyframes indented as blocks — so the file reads like SCSS rather than like a minified or machine-generated dump. It validates structure first, with the same check the CSS Beautifier uses, so a missing brace is reported with a line and column instead of being silently 'repaired'. And, if you switch it on, it extracts repeated literals into $variables — the one transformation described in the next answer. What it never does is guess structure: no nesting is inferred from selector prefixes, because a guess about your intent that is wrong changes specificity and breaks the page.
How does variable extraction work, and is it safe?
You opt in, and choose how many times a literal must repeat (three by default). Colour literals (hex, rgb(), rgba(), hsl(), hsla()) and length literals (16px, 1.5rem, 50%, …) that appear at least that often in declaration values are replaced by $color-1, $length-1, … and defined at the top of the file. Every extraction is listed above the result with its exact count — count first, then replace exactly what was counted; if the two ever disagree the conversion is discarded. Literals are never taken from inside strings, comments or url() bodies, never from custom property definitions (a --brand: declaration is a definition, and rewriting it would change what it defines), and zero lengths are never extracted. A colour's insides are also off-limits: the 50% inside hsl() is part of the colour, not a separate length.
Why doesn't it nest my selectors? .card .title looks nestable.
Because it only looks nestable. .card .title, .card .title span and .card-large .title share a prefix and share nothing else — nesting is a statement about how you organise the stylesheet, and a converter that infers organisation from spelling will eventually group rules that were deliberately separate, or (worse) change which rules exist at all when a shared prefix appears by coincidence. Nesting by hand, once you see the formatted layout, takes seconds per rule and is a decision you make with the design in front of you. The mechanical half — layout and variables — is what this tool does.
Will the output compile back to the same CSS?
Yes — that is the conversion's own acceptance test, run on every conversion before you see the result. The output is parsed again, every $variable is substituted back to its literal, and the result must equal the input rule for rule and declaration for declaration. If it ever does not, the output is discarded and you get an error instead of a quietly different stylesheet. Declarations are never reordered, merged or dropped, and values pass through byte-for-byte apart from the variable substitutions you opted into.
What SCSS features does the output use?
Deliberately few: $variables, and the block structure SCSS shares with CSS. No mixins, no @extend, no nesting, no maths — the output stays one step from the input, so a team adopting SCSS gradually can diff the conversion meaningfully and introduce real SCSS features rule by rule, by hand, where they pay. If you want a converter that rewrites a stylesheet into idiomatic SCSS wholesale, that tool would be making design decisions; this one declines to.
What about @media, @keyframes and @font-face?
They keep their structure: rules inside @media and @supports stay inside, indented one level deeper; @keyframes keeps its from/to/percentage blocks; @font-face and @page keep their declarations. Variable extraction reaches declarations inside all of them, because a brand colour repeated in a keyframe is the same literal as anywhere else. At-rules this converter does not know are still preserved — prelude and block pass through in the same layout — because dropping an unknown at-rule would be data loss, not conversion.