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, configuration, and export/protection flags.

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

In Jess, @-use is the namespace form for JS/TS/JSON modules. Like Sass, it can infer the namespace from the module name when as is omitted. @-from is the ESM-style form for named imports or import * as ns namespace imports.

When parsing SCSS, Jess will treat @use as a stylesheet import by default, but if the @use path clearly targets a script module (for example ./tokens.js, ./tokens.ts, ./tokens.json), it will parse to a JS import node instead.

@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 normalization 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".

  • Quoted string keys with no interpolation are treated as plain keys.
    • "regular" becomes the bare regular, which is the string key "regular".
  • 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.