Skip to main content

Sass (SCSS) compatibility

Jess can parse SCSS input and move supported Sass constructs into Jess's native model.

Native Jess uses $name for live/current references and $^name for scoped/final lookup.

What SCSS adds beyond CSS​

Sass extends CSS with (at least) the following features:

  • Variables: $var: value; (Sass variables)
  • Interpolation: #{...} in selectors, property names, and values (Interpolation)
  • Mixins/includes/content blocks: @mixin, @include, @content, using (...) (Mixins)
  • Control directives: @if, @for, @each, @while, @error, @warn, @debug (Control directives)
  • Module system: @use, @forward, configuration via with (...) (use, forward)
  • Maps and lists: including built-in helpers like map.get() (Maps, sass:map)
  • Extend + placeholders: @extend and %placeholders (extend, placeholders)
  • @at-root: explicit control over nesting output (at-root)
  • Nested declarations (“property nesting”) (declarations)

At-rule mapping: Sass → Jess​

Jess keeps compiler-level at-rules prefixed with - to avoid collisions with the evolving CSS standard library.

@use (stylesheets) → @-compose​

Sass:

@use './theme' as theme;
.box { color: theme.$primary-color; }

Jess equivalent:

@-compose './theme' as theme;
.box { color: $theme.primary-color; }

Jess stylesheet imports support namespaces, isolated scope, configuration, and forwarding through @-export.

@use (scripts) → @-use / @-from​

@-use imports a JS/TS/JSON namespace, while @-from imports named, default, or namespace exports. When parsing SCSS, Jess treats @use as a stylesheet import by default; a path that clearly targets .js, .ts, or .json is a script or data module. Built-in sass:* modules are available without a script runtime. See Modules & imports for the canonical syntax and runtime requirements.

@forward (SCSS) → @-export​

Jess models Sass forwarding semantics with @-export (see At-rules).

Sass maps → Jess collections​

Sass maps become Jess collections.

Sass:

$font-weights: ("regular": 400, "medium": 500, "bold": 700);

Jess shape:

$font-weights: {
regular: 400;
medium: 500;
bold: 700;
}

Key comparison rules​

A collection key is a value, matched by value equality — the same model Sass maps use, where map.get($m, 1px) must match the number 1px and not the text "1px".

  • A quoted key such as "regular" remains a Quoted value in the current AST, while bare regular is a Keyword. Sass equality treats those keys as equal; quote-normalized storage and output are recorded follow-up work.
  • Other Sass map keys (numbers, colors, lists) stay the values they are; see Namespaces, collections, and maps for the key model and for why a numeric subscript is a position rather than a key.

map-get() and map.get() → Reference lookups​

Sass commonly accesses map keys via:

@use "sass:map";

.box {
font-weight: map.get($font-weights, "medium");
}

Jess does not treat this as a normal function call. Instead, the SCSS parser desugars:

  • map-get($map, $key[, ...])
  • map.get($map, $key[, ...])

into a Reference lookup chain:

  • Evaluate the map expression (often $font-weights)
  • Look up the key(s) as nested property/index accesses

This is the same core mechanism Jess uses for $theme.colors.primary and array/map-like access.

Slash division and arithmetic​

Sass treats / as either:

  • division
  • or a plain slash separator in a value

Jess does not try to preserve the whole ambiguity set when importing SCSS.

Jess supports the clear arithmetic forms:

  • math.div(...)
  • isolated parenthesized arithmetic like (15px/30px)

Jess does not try to infer division from every legacy Sass slash form in general value position. If a slash form is ambiguous, Jess prefers to keep it as a slash-separated value rather than guess.

This aligns with Jess’s own language model:

  • real math should be explicit
  • imported SCSS arithmetic should only be recognized when the syntax is clearly arithmetic
  • native Jess expressions use $(...)

SCSS interpolation mapping​

Jess distinguishes:

  • a variable reference: $var
  • an expression: $(...)
  • an interpolation: ${ident} (identifier and string positions; ${[ident]} to read a property rather than a variable)
  • a lookup: $[key] / $m[key] (value position)

#{$...} in selector/property names​

When SCSS interpolation appears in selector or property-name positions:

  • #{$ident} (single variable reference) maps to ${ident}
  • More complex interpolation expressions are rewritten through a generated private temp variable (_tmp_<n>), then referenced as ${_tmp_<n>}

This keeps selector/property interpolation identifier-only while still supporting complex expressions.

#{$...} in values/strings​

In value/string contexts, SCSS interpolation may map to:

  • ${ident} inside a string
  • $(...) for expression interpolation in value position

For example, #{$color} can become $($color) when live-reference expression semantics are needed.

Interpolation in mixin names​

SCSS does not allow interpolation in mixin names. Jess follows that behavior:

  • @include foo-#{$bar}() is rejected
  • @mixin foo-#{$bar} is rejected

Placeholder selectors (%foo)​

Sass placeholder selectors don’t emit CSS unless they’re extended.

Jess parses:

  • %foo as selector name \foo

This name conversion makes placeholders syntactically distinguishable from normal selectors while still being a selector-like key in the registry.

Placeholder visibility (current state)​

Sass placeholders are “silent” until extended. In Jess today, placeholder parsing is supported, but full Sass-equivalent placeholder visibility behavior remains partial. Use this area with extra verification in migration-heavy code paths.

Cross-module behavior​

Sass’s module system allows extending placeholders across module boundaries, with private placeholders (leading _ or -) restricted to their defining module (see Sass’s placeholder docs above). Jess aims for parity here, but behavior is still being finalized.

Variable flags: !global and !default​

Sass supports !global and !default on variable assignments (variables).

Jess’s SCSS support intentionally does not implement Sass’s full !global shadowing model. It lowers !global to the scoped/final set-existing/no-shadow operation ($^name := value). SCSS !default corresponds to the scoped/final conditional operation ($^name?: value). Both $name: and $^name: create or update both bindings in native Jess.

!default exists in Sass primarily to support module configuration (@use ... with (...)). Jess will align with Sass’s @use configuration behavior by ensuring configurable variables are explicitly marked and handled accordingly.

Accumulating a variable inside a loop​

Sass lets a bare $x: inside a control-flow block update a variable declared outside it — the pattern behind a running total. Jess preserves this:

$sum: 0;
@for $i from 1 through 3 {
$sum: $sum + $i;
}
.total { width: $sum; }
.total {
width: 6;
}

Inside @if / @else / @for / @each / @while, a bare $x: reassigns the nearest existing binding, and only declares a new block-local variable when the name is not bound anywhere. A $x: at the top level, or directly inside a ruleset, stays an ordinary declaration. This is the same "optional shadow" write that native Jess spells ::= — reading SCSS lowers in-loop declarations to it so accumulators behave as they do in Sass.

Compile-time diagnostics: @debug, @warn, @error​

Sass's diagnostic at-rules are recognized. They never produce CSS:

  • @debug "…"; and @warn "…"; surface a message during compilation and let it continue.
  • @error "…"; aborts the compile with its message.
@warn "use the spacing tokens instead";
.a { color: red; }
.a {
color: red;
}

The @warn message is reported during compilation; the CSS is still produced. An @error reached during evaluation stops the compile with its message instead.

These are SCSS-only. In native .jess (and in CSS and Less) @debug / @warn / @error are not diagnostic directives.