CSS Tools
CSS Preprocessor Converter
One page for every cross-dialect trip — LESS, SCSS, indented Sass and Stylus, in any of the twelve directions.
In short
Convert a stylesheet between LESS, SCSS, indented Sass and Stylus: choose a source and a target, and sigils, braces and nesting are translated for you.
By NaveenKumar T · Updated
Learn: Sass vs SCSS vs LESS vs Stylus: Choosing a CSS Preprocessor
- Twelve directions, one page
- Variable sigils translated
- Braces ⇄ indentation
Runs entirely in your browser — nothing you paste or open here is uploaded, logged, or stored.How we handle data →
Example
Example: LESS to SCSS
A notice component in LESS — three @variables, a nested BEM child, an &:hover state and an in-rule media query — converted to SCSS.
@brand: #6366f1;
@radius: 8px;
@gap: 12px;
.notice {
padding: @gap;
border-radius: @radius;
border-left: 4px solid @brand;
.notice__title {
margin: 0 0 @gap;
color: darken(@brand, 15%);
}
&:hover { border-left-color: lighten(@brand, 10%); }
@media (max-width: 480px) { padding: @gap 8px; }
}$brand: #6366f1;
$radius: 8px;
$gap: 12px;
.notice {
padding: $gap;
border-radius: $radius;
border-left: 4px solid $brand;
.notice__title {
margin: 0 0 $gap;
color: darken($brand, 15%);
}
&:hover {
border-left-color: lighten($brand, 10%);
}
@media (max-width: 480px) {
padding: $gap 8px;
}
}
Every @variable becomes its $ twin — in the declarations at the top and inside darken($brand, 15%) and lighten($brand, 10%) — while @media keeps its @ because it is on the at-rule keyword list. The nesting, the &:hover rule and the media query are rebuilt from the parsed tree, and the result is printed with prettier's SCSS parser.
Learn more
Pick a source, pick a target
Four preprocessor dialects give twelve ordered conversions, and twelve near-identical pages would help nobody. This is one converter with two selectors: Convert from and Convert to. Whatever you pick, the stylesheet is parsed once into a rule tree — from braces or from indentation, depending on the source — and then printed back out in the target's grammar. Because the intermediate step is a real tree rather than a chain of find-and-replace passes, nesting depth, comma-separated selector groups, & parent references and @media blocks all survive the trip intact.
The four dialects differ on two axes and, structurally, on almost nothing else. Punctuation: LESS and SCSS use braces and semicolons, while indented Sass and Stylus carry structure in whitespace — and Stylus goes further, making the colon after a property optional and accepting bare name = value assignments. Sigils: LESS spells a variable @name, SCSS and indented Sass spell it $name, and Stylus accepts either plus the bare form. Nearly everything the converter does is a consequence of those two differences, which is also why the conversions are so reliable for the static parts of a stylesheet.
What maps, exactly
The table below describes what this converter really does, which is not the same as what a full compiler could do. Nothing here is evaluated: variables are renamed rather than resolved, so a token file converted from LESS to SCSS still reads as a list of $variables rather than a list of colours, which is exactly what you want when the point is to keep maintaining the sheet in a new dialect.
The columns show the three transformations that account for every direction. A brace-to-brace conversion (LESS ⇄ SCSS) only touches sigils and formatting. A conversion into an indented target (Stylus or Sass) additionally drops braces and semicolons and rebuilds the blocks as indentation. A conversion out of an indented source does the reverse, first normalising Stylus's optional punctuation — restoring the colon on prop value lines and turning name = value assignments into proper variable declarations — so the tree is well-formed before it is printed.
| Feature | LESS → SCSS | SCSS → LESS | → Stylus / Sass | Notes |
|---|---|---|---|---|
| Variables | @brand becomes $brand | $brand becomes @brand | Emitted as $brand = value (Stylus) or $brand: value (Sass) | The rename is text-level across the whole sheet — declarations and function arguments alike. Values are renamed, never resolved. |
| Nesting | Preserved | Preserved | Becomes indentation | Rebuilt from the parsed rule tree at any depth, with selector lists kept grouped. |
| & parent references | Kept verbatim | Kept verbatim | Kept verbatim | All four dialects spell & identically, suffix forms such as &__title and &--active included. |
| Media queries | Preserved | Preserved | Become block headers | @media, @supports, @keyframes, @layer and the other at-rules are keyword-protected and never renamed. |
| Mixins | @mixin / @include copied as written | .mixin() calls copied as written | Copied as written | No mixin grammar is translated in any direction — these are the lines to rewrite by hand, and the target compiler will reject them until you do. |
| Functions | darken(@c, 10%) → darken($c, 10%) | darken($c, 10%) → darken(@c, 10%) | Arguments keep their sigil | Only the sigil changes; function names are never mapped, so check each call against the target's built-in list. |
| Interpolation | Not translated | Not translated | Not translated | @{name} and #{$name} contain a brace, which the parser reads as a block opener — remove interpolated selectors before converting, then paste them back. |
| Comments | Dropped | Dropped | Dropped | /* */ and // are stripped before the tree is built, and blank lines are not preserved either. |
The dynamic layer is yours to port
Everything a preprocessor does at build time — mixins, functions, conditionals, loops, @extend, maps — is where the dialects genuinely diverge, and no converter can bridge that mechanically without becoming four compilers at once. So those lines are emitted verbatim, which has the useful property of making them obvious: convert a file, and the handful of lines that still look like the old dialect are precisely your to-do list. For a typical component sheet that is two or three mixin calls against forty lines of structure that came across untouched.
Two habits make the result better. Format first: a stylesheet printed one declaration per line converts more predictably than one with three rules crammed onto a line, so run it through the LESS formatter or SCSS formatter before converting. Then verify: paste the converted sheet into the target's real compiler — SCSS / Sass to CSS for dart-sass, LESS to CSS for less.js — and let it tell you which lines still need work, with a line number attached.
Finally, be clear about what the conversion is for. Migrating a codebase between preprocessors is a real project, and this page automates its tedious half: the sigils, the punctuation and the whitespace that are error-prone by hand and worthless to review. The interesting half — deciding which mixins survive, which become CSS custom properties, and which were never needed — stays with you. If what you actually want is plain CSS rather than another preprocessor, skip this page and compile: SCSS / Sass to CSS and LESS to CSS run the real engines, and CSS to SCSS / Sass handles the reverse trip.
Help
Frequently asked questions
Everything you need to know about the CSS Preprocessor Converter.
01How do I use the preprocessor converter?
Set Convert from to the dialect you are holding and Convert to to the one you need, paste the stylesheet (or load a file), and press Convert. The sheet is parsed once into a rule tree and re-emitted in the target grammar. Twelve directions are available — every ordered pair of LESS, SCSS, indented Sass and Stylus — and picking the same dialect twice is refused rather than quietly doing nothing.
02How does the converter tell @media apart from a LESS @variable?
By a keyword list. When the sigil rename runs, every @-prefixed name is checked against the CSS and Sass at-rules — media, supports, import, charset, keyframes, font-face, page, namespace, layer, container, property, document, plus mixin, include, extend, use, forward, if, else, each, for, while, function, return and content. Anything on that list keeps its @; anything else is treated as a variable and becomes $name. The protection works in both sigil directions, so @media never turns into a fake variable on the way back to LESS either.
03Do mixins and functions convert?
No — they are copied through as written, and that is deliberate rather than a shortcut. Every dialect spells the dynamic layer differently: SCSS uses @mixin and @include, LESS uses .mixin() calls and guards, indented Sass adds the = and + shorthands, and Stylus defines mixins as plain functions and even lets you call them like properties. There is no mechanical mapping between those grammars, so the converter leaves the lines intact and visible instead of guessing. In a typical component sheet that is a handful of lines against dozens that convert automatically — and the target's compiler will reject them until you rewrite them.
04Why did my interpolated selector come out mangled?
Because interpolation contains a brace, and the parser reads braces as block openers. A LESS selector written .icon-@{name} or an SCSS one written .icon-#{$name} makes the parser think a new rule has started, which corrupts everything after it. Neither form is translated to the other, either. Pull interpolated selectors — and ~"escaped strings" that contain braces — out of the sheet before converting, then paste them back into the result. Comments are also dropped, so re-add licence headers afterwards.
05Can I convert to plain CSS here as well?
Not on this page — this one moves a sheet sideways between preprocessors, where no evaluation is needed. Going down to CSS means actually running a compiler, which the dedicated pages do with the real engines: [SCSS / Sass to CSS](/scss-to-css) runs dart-sass and [LESS to CSS](/less-to-css) runs less.js, both expanding mixins and computing functions. A useful two-step workflow is to convert here, then paste the result into the matching compiler to confirm it builds.
Keep working