SASS to SCSS Converter

SCSS is the same language with the punctuation put back. Paste an indented .sass file below and the nesting becomes braces while every declaration picks up its semicolon. Interpolation, quoted braces, nested properties and the shorthand directives are read properly, and the rail beside the output names every line the parser had to decide about.

SASS to SCSS conversion workbench

  • Converts as you type
  • Expands @mixin shorthand
  • Runs offline in the tab
Indent
Brace
Indented SASS0 lines .sass
SCSS output0 lines .scss

Why a stylesheet moves back to braces

The indented syntax arrived first, in 2006, and still has people who prefer it. Most Sass work now happens in SCSS, and files drift in this direction for practical reasons rather than taste. Five of them come up in almost every migration.

None of this makes the indented syntax a mistake. A team writing every line in house, with nobody pasting CSS from outside, keeps shorter files and cleaner declarations by staying where they are. Convert when the friction shows up in review, not because braces are the default.

The five rewrites this converter performs

Sass reads both syntaxes with one compiler, so this is a reformatting job rather than a translation. Five rewrites cover a normal stylesheet. Everything else copies across as written.

  1. Indentation becomes braces

    Each indent level opens a block and the dedent closes it. The parser looks ahead one line to work out whether a line opens a block or ends a statement, so a selector with deeper lines under it gets braces while a declaration does not.

    SASS in
    .card
    padding: 1rem
    &:hover
    border-color: $brand
    SCSS out
    .card {padding: 1rem;&:hover {border-color: $brand;}}
  2. Line ends become semicolons

    A declaration in the indented syntax finishes where the line finishes. SCSS wants the semicolon spelled out. At-rules with no block attached take one too, so imports and module loads are covered by the same pass.

    SASS in
    @use "sass:math"
    $gutter: 24px
    .grid
    gap: math.div($gutter, 2)
    SCSS out
    @use "sass:math";$gutter: 24px;.grid {gap: math.div($gutter, 2);}
  3. Shorthand directives get written out

    The indented syntax has a shorthand pair with no SCSS equivalent. =name defines a mixin and +name calls one. Both get expanded to the long form, which is the only spelling the brace syntax accepts. The rail counts how many were rewritten.

    SASS in
    =card-shadow($blur: 24px)box-shadow: 0 8px #{$blur} rgba(0,0,0,.08).panel
    +card-shadow(32px)
    SCSS out
    @mixin card-shadow($blur: 24px) {box-shadow: 0 8px #{$blur} rgba(0,0,0,.08);}
    .panel {@include card-shadow(32px);}
  4. Backslash continuations fold into one line

    A long value in a .sass file runs onto a second line with a trailing backslash, the one continuation marker the syntax accepts. SCSS ends the declaration at the semicolon instead, so the fragments join and the backslash goes away.

    SASS in
    .panel
    transition: background 120ms ease, \
    box-shadow 120ms ease
    SCSS out
    .panel {transition: background 120ms ease, box-shadow 120ms ease;}

    Joined values stay on one line. Rewrap them by hand where your line limit complains, since SCSS allows a value to sit across several lines with no marker at all.

  5. @else rejoins the brace of its @if

    This is the rewrite a line-by-line script gets wrong. In the indented syntax an @else sits at the same depth as its @if. Under braces it has to follow the closing brace on the same line, or Sass reports an else with no matching if and the build stops.

    SASS in
    @if $theme == dark
    .page
    background: #111
    @else
    .page
    background: #fff
    SCSS out
    @if $theme == dark {.page {background: #111;}} @else {.page {background: #fff;}}

    Chains of @else if follow the same rule, and the whole chain gets walked. Switching the brace control above to next-line style leaves the closing brace alone and puts the else underneath it, which is the shape Allman-style formatters expect.

Judgment calls the parser makes for you

Some lines have more than one reasonable reading. Here is what happens to each one and the reasoning behind it.

Braces inside a value stay inside the value
#{$size} and a string like content: "{ a }" both carry braces with no block behind them. Quote state and interpolation depth are tracked apart from nesting depth, so neither one opens a level. A converter built on find and replace either eats the expression or pushes every line below it one level too deep.
A nested property keeps its trailing colon
font: with nothing after it opens a namespace rather than a declaration. The output writes font: { and the children keep their own colons. Dropping the parent colon turns the line into a selector named font, and the compiler stops at the first child.
A loud comment gets its closing marker back
A comment in the indented syntax ends when a line returns to the parent indent, so plenty of .sass files never write the */ at all. The output adds it where the indentation ran out. Re-read any comment carrying ASCII alignment, because the continuation lines get re-indented to the block they sit in.
A trailing comment keeps its position
The semicolon goes in front of the comment rather than at the end of the line. background: transparent // note becomes background: transparent; // note. Appending the semicolon after the comment text would bury it and leave the declaration unterminated.
A childless selector becomes an empty block
A selector with nothing indented under it has no declarations to wrap, so the output writes .name {} and the rail flags it. Sass drops empty rules at compile time, which means these are either a leftover or a sign the indentation slipped a level.
SCSS input is left untouched
When most lines already end in a brace or a semicolon, the input is SCSS and a conversion pass would nest every block a second time. The text passes through unchanged with a warning. Send it the other way through the SCSS to SASS converter.

Moving a project across, one partial at a time

A whole-repo conversion in a single commit is close to unreviewable, since every line in the diff changed. Splitting the work keeps the history readable and the build green throughout. Sass reads .sass and .scss files in one project and imports cross the boundary in both directions, so a half-converted repo compiles without complaint.

  1. Compile and keep the current CSS. Save the built stylesheet before touching anything. This file is what you compare against later, and the comparison is the only check proving nothing moved.
  2. Start with leaf partials. Files nothing imports carry the least risk. Variable and mixin libraries come after, since a slip there shows up in every rule at once.
  3. Convert, then rename. Paste one file above, copy the SCSS out, then change the extension. Import paths stay as they are, because @use and @import reference a file without its extension.
  4. Read the rail before committing. The notes name empty rules, uneven indentation and expanded shorthands. Each of those marks a spot where the source was ambiguous and a human should look.
  5. Compile again and diff the CSS. Byte-identical output is the target. A difference means the nesting shifted, and the diff points straight at the rule.
  6. Keep reformatting out of behavior commits. One commit per batch of converted files with no other edits inside it. Reviewers skim a pure reformat and read a real change properly.
  7. Switch the tooling on last. Add Prettier and the stylelint SCSS rules once every extension has changed, so the formatter does not rewrite half-migrated files underneath you.

Watch the files you have not converted yet. Mixed tabs and spaces are a hard error in a .sass file and a non-issue in .scss. A source file Sass has been rejecting for weeks starts compiling the moment it converts, and the rule it produces might not be the one anyone expected.

Where this converter stops

Questions about converting SASS to SCSS

What changes, what survives, and how to migrate a project without breaking the build.

What changes when I convert SASS to SCSS?

Punctuation and nothing else. Indentation levels become braces, line ends become semicolons, and the = and + shorthands get written out as @mixin and @include. Variables, mixins, functions, control flow, partials and the module system behave identically in both syntaxes, because one compiler reads them both. The file gets longer and the meaning stays the same.

Will my compiled CSS change after the conversion?

No. Compile the .sass file, convert it, compile the .scss result, and the two stylesheets match byte for byte. Doing exactly that is the best check available during a migration. If the two outputs differ, the nesting shifted somewhere, and the diff between the compiled files points at the rule that moved.

What happens to =mixin and +include shorthand?

Both get expanded. =card($size) becomes @mixin card($size) and +card(2rem) becomes @include card(2rem). Neither shorthand exists in SCSS, so leaving them alone would produce a file Sass refuses to parse. The rail reports how many were rewritten, which is a quick way to see how much of the source leaned on them.

Why does @else break in other converters?

Because the two syntaxes attach it differently. In indented SASS the @else sits on its own line at the same depth as the @if. In SCSS it has to sit on the same line as the closing brace of the if block, written as } @else {. A converter working line by line emits the closing brace, then a bare @else, and Sass reports an else with no matching if. The chain is tracked here, including @else if links.

Do I have to convert every file at once?

No, and doing it gradually is easier to review. Sass picks the syntax per file from its extension, and imports cross between the two in either direction. A .scss entry point pulls in .sass partials while you work through them. Convert leaf partials first, keep each batch in its own commit, and compile after every batch.

Do my import paths need updating after I rename the files?

Usually not. @use and @import reference a file by path without the extension, so @use "components/buttons" keeps working after buttons.sass becomes buttons.scss. Update the path only where someone wrote the extension out in full, which is rare, or where the rename also moved the file to a different folder.

Are my comments kept?

Yes, both kinds. Silent // comments pass through in place, and a trailing one stays on its line with the semicolon inserted in front of it. Loud comments keep their text, and the closing marker gets added when the source relied on indentation to end the comment. Anything that depended on column alignment is worth re-reading, since continuation lines are re-indented to their block.

What if my file mixes tabs and spaces?

You get output plus a warning. Sass itself refuses a .sass file with mixed indentation characters, so a file in that state was already failing to compile. This parser measures a tab as four spaces to produce something useful, though the depths are a guess. Reindent the source with one character, convert again, and compare the two results before trusting either.

Does my stylesheet get uploaded anywhere?

No. The parser runs inside this page and converts on each keystroke in your browser. No request goes out after the page has loaded, so the tool keeps working with the network disconnected. Nothing is stored between visits and closing the tab clears both panes.