Variables
Jess has two explicit variable-reference modes.
| Reference | Meaning |
|---|---|
$foo | Live reference: read the value currently bound for foo at evaluation time. |
$^foo | Scoped/final lookup. |
Choosing a reference
Use $foo when the value should be live at the point the expression evaluates.
Use $^foo when you need the scoped/final lookup behavior.
.card {
color: $theme; // live reference
border: $^theme; // scoped/final lookup
}
Variables are not expression names
$color reads the variable named color. $(color) does not; inside an
expression, color is an ordinary value token. To use a variable inside an
expression, keep the variable reference explicit:
$color: red;
.card {
a: $color; // variable lookup -> red
b: $(color); // value token -> color
c: $($color); // expression containing a variable lookup -> red
}
Declarations and assignments
There are four assignment operators. They differ only in which binding they write and what happens when the name is not yet bound:
| Operator | Name | What it does |
|---|---|---|
$x: value | declare | Bind x in the current scope. If an outer scope already binds x, this shadows it locally — the outer binding is left untouched. |
$x := value | reassign | Write the nearest existing binding, wherever it lives up the scope chain. If x is not bound anywhere, this is a compile error. |
$x?: value | assign-if-unset | Bind x only if it is not already bound. If x already has a value, nothing happens. This is the equivalent of Sass's !default. |
$x ::= value | optional shadow | Write the nearest existing binding, or — if x is unbound anywhere — declare it locally. It always writes; only the fallback declaration is conditional. |
$x: 1;
$x := 2;
.a { width: $x; } // 2 — reassigned the existing binding
Reassigning a name that does not exist is an error, rather than quietly creating it:
$x := 2; // error: live variable $x is undefined
?: assigns only when the name is unset, so an earlier value wins:
$brand: #06c;
$brand?: #f00;
.a { color: $brand; } // #06c — the ?: was skipped
::= is for Sass conversion, not hand-written Jess
::= exists so an automated SCSS-to-Jess conversion can preserve one Sass rule:
a bare $x: written inside a control-flow block reassigns an outer variable when
one exists, which is what a @for / @each / @while accumulator relies on.
When Jess reads SCSS, it lowers those in-loop $x: declarations to ::= for you.
In hand-written .jess, prefer the explicit operators — : when you mean
"declare here" and := when you mean "reassign an existing variable." Reach for
::= only when you genuinely want "reassign if it exists, otherwise declare."
Scope
A declaration's scope is the nearest enclosing { } block. Rulesets, mixin
bodies and function bodies open a scope — and so do control-flow blocks
($if / $for / $while). Braces mean the same thing everywhere: a variable
declared with : inside a block is local to it and is gone after the closing
brace.
To update a variable that lives outside the block, reassign it with :=:
.box {
$my-color: red;
$if (true) {
$my-color := blue; // updates the outer binding
}
color: $my-color; // blue
}
Writing $my-color: blue inside the $if would declare a block-local variable
instead, and color would be red.
The same applies to a loop that builds up a value — $total := $($total + $i)
reassigns, while $total: … would start a fresh binding each pass. See
Conditionals and iteration.
$if and $whileToday only $for enforces this. A : declaration inside $if or $while
still leaks into the surrounding block, and := inside $for fails to find an
outer binding — jess#232. Write
:= when you mean "update the outer variable" and your code will be correct
either way.
Live and scoped forms
Every operator also has a $^ scoped/final form ($^x: …, $^x := …,
$^x?: …). The sigil chooses which lookup the write and later reads use — live
($x) or scoped/final ($^x) — as described above; it does not change the kind
of binding created.
SCSS keeps its own $name: value, !default, and !global syntax at the input
boundary; see Sass (SCSS) compatibility.
Block-valued assignments
Normally, adjacent declarations need semicolon separators. Block-valued
assignments are the exception: a variable can be bound to a
collection, an anonymous mixin, or a
function, and that assignment ends at the block's closing brace.
The trailing ; is optional even when another declaration follows:
$colors: {
primary: #06c;
}
$mixin: @{
color: red;
}
$double: @($n) > {
result: $($n * 2);
}
All three are also valid written with a ; after the closing brace. Write whichever
you prefer, but you never need one.
Block values stand on their own as the assigned value. To combine data from a block with other values, bind the block first and compose from the binding:
$shadow: {
blur: 4px;
}
$rule: 0 0 $shadow.blur #000;
.box {
box-shadow: $rule;
}
→
.box {
box-shadow: 0 0 4px #000;
}
The same rule applies to collections, anonymous mixins, and function literals.