Sass never reaches the browser. CSS does.
Variables, nesting, mixins, and loops exist for the people writing stylesheets. No browser parses any of them. A compiler flattens the whole file into ordinary rules first, and what ships is plain CSS. This page runs that compile step in your tab so you read the source and the flattened result side by side.
Type on the left, wait half a second, read the right. The pause keeps a half-finished brace from throwing an error at you mid-keystroke. Nothing uploads anywhere, because the compiler is a WebAssembly build of LibSass running in the same page.
What the compiler does to each feature
| Feature | SCSS you write | CSS you get | Watch for |
|---|---|---|---|
| Variable | $brand: #cd6799 | The literal hex, everywhere it appeared | Baked in at compile time. Runtime theming needs CSS custom properties instead. |
| Nesting | .card { .title { } } | .card .title | Every level adds specificity you pay for later. |
| Ampersand | &:hover, &__title | .card:hover, .card__title | The ampersand glues with no space. A stray space changes the meaning. |
| Mixin | @include button(red) | The full declaration block, copied in | Thirty call sites write thirty copies of the block. |
| Placeholder | @extend %btn | One rule with a grouped selector list | Output lands where the placeholder sits, not where you extended it. |
| Loop | @each $k, $v in $map | One rule per iteration | A twelve-item map writes twelve rules whether you use them or not. |
| Math | $gap * 2 | 16px | Resolved during compilation. The browser sees a finished number. |
The mixin and placeholder rows are the interesting pair. Mixins duplicate a block at every call site. Placeholders write the block once and append selectors to it. Gzip flattens most of the size difference over the wire, so pick on intent: mixins when the block takes arguments, placeholders when the block is byte-identical every time.
Nesting is where the output surprises people
Five levels of indentation read like a tidy outline in SCSS. Compile it and the outline becomes one long descendant selector.
.card {.body {.list {li {a { color: #cd6799; }}}}}.card .body .list li a {color: #cd6799;}That selector outranks nearly anything you write later, and it snaps the moment a component moves in the markup. Two levels is a workable ceiling. Past that, build flat class names with the ampersand rather than stacking descendants.
This page compiles with LibSass, and Dart Sass differs
LibSass was retired in 2020. It froze at the Sass language as it stood then, so a file written against the current Dart Sass module system will not compile here. Four gaps come up most often:
@useand@forwardfail. The module system never landed in LibSass. Rewrite to@importbefore pasting, or compile locally.math.div($a, $b)fails. LibSass reads slash division, so write$a / $bin the copy you paste here.- Namespaced built-ins fail.
color.adjust()andmap.get()belong to the module system. Their older global names,adjust-color()andmap-get(), work fine. @import "buttons"finds nothing. A browser tab has no project folder. Partials do not resolve, so paste their contents inline.
For one component, a variables file, or a snippet lifted from a tutorial, none of this bites. For a design system built on @use, run the sass CLI on your machine and treat this page as a scratchpad for the piece you are debugging.
Output style changes the bytes, not the rules
Four styles ship with the compiler. All four produce the same selectors and declarations:
- Expanded puts one declaration per line. Read the output in this mode.
- Nested indents child rules to mirror the source hierarchy. Useful when you are tracing which SCSS block produced which rule.
- Compact collapses each rule onto a single line. A middle ground for diffing.
- Compressed strips whitespace, drops the last semicolon in each block, and shortens some colors. Ship this one.
The size counter under the panes updates with the style, so switching between Expanded and Compressed shows the transfer cost of the file before gzip touches it.
Where this converter stops
- No source maps. Mapping a compiled line back to its SCSS origin needs a local build with
--source-map. - No vendor prefixes. Sass has never added them. Run the output through Autoprefixer or PostCSS if you support older targets.
- No file imports. There is no filesystem behind the page, so multi-file projects have to be pasted as one block.
- One error at a time. LibSass reports the first failure and stops. Fix it, and the next one surfaces.
- Main-thread compilation. Past a few thousand lines the pause between typing and output grows noticeable on slower machines.
Before the output goes into a repo
- Scan the compiled selectors for depth. Three parts is a smell, five is a bug waiting for a markup change.
- Look for the same block repeated at several call sites. A mixin with no arguments usually belongs in a placeholder.
- Confirm which colors got baked.
lighten($brand, 20%)resolves to a fixed hex. Values meant to change at runtime belong in custom properties, which pass through the compiler untouched. - Run the result through a formatter or minifier to match your project's house style. The CSS Beautifier and CSS Minifier both handle it.
Going the other direction, from a finished stylesheet back into nested SCSS, is a different job with different rules. The CSS to SCSS Converter handles that side.
