Jess's at-rules
One of the ironies about the popularity of both Less and Sass is they became too popular, and their syntax was too intuitive. In the case of Less, this meant that useful functions like if() had syntax conflict as soon as a CSS-native if() was adopted (in 2025), as well as many other Less functions.
Sass has had much bigger syntax conflicts, mostly in the space of at-rules. Sass has a huge number of at-rules, and some of those at-rules have been adopted by the CSS working group.
Many in the CSS community wanted to use Sass's @if as a new conditional rule, but ultimately settled on @when to avoid breaking existing Sass code. That caution makes sense: Sass syntax has had to carry a lot of historical compatibility weight, and that's why that syntax is discouraged. Jess is designed with more future-CSS headroom, so new native features are less likely to collide with compiler syntax.
One of Jess's goals is to make the syntax as future-proof as possible. So, even in the case where Jess supports Less and Sass behavior, compiler at-rules are prefixed with dashes to not cause clashes.
@-compose
This is probably the most important at-rule. It's what you will use to compose together different stylesheets. It is most similar to, and largely a drop-in replacement of Sass's @use at-rule.
That is, it will render
Here's an example:
- Jess
- Less 5+
- Sass
@-compose './theme' as theme;
.box {
color: $theme.primary-color;
}
@compose './theme' as @theme;
.box {
color: @theme[primary-color];
}
@use './theme' as theme;
.box {
color: theme.$primary-color;
}
Why @-compose and not @-use, similar to Sass? A couple reasons.
- "Use" typically implies an import of values, but values and variables can actually flow into the stylesheet you're composing with (which can change how it evaluates and compiles), which we'll get to!
- We're not just importing values; composing also renders rules. In addition, those rules can be altered by extending them, which also is contrary to how "use" works in most languages.
- We have a use case for
@-usewhich fits into the "use" semantics a bit better.
Composing with a namespace
Like Sass, namespaces for @-compose and @-use are set automatically based on the file name or path. But you can also set a namespace explicitly with the as keyword.
@-compose './theme.jess'; // is the same as...
@-compose './theme.jess' as theme;
You can compose without a namespace using the * symbol.
@-compose './theme.jess' as *;
.box {
color: $primary-color; // from theme.jess
}
Composing without a namespace is discouraged, as it can be harder to reason about where variables and values come from when you have multiple files.
with and set
Jess currently supports both set and with when composing stylesheets. Both
forms provide values to the composed module; the difference is how long that
configuration applies:
setestablishes a one-time, compilation-wide configuration for the resolved module. Later imports of that module reuse the same values.withprovides overrides for the current compose only. A later compose starts from the module's defaults unless it supplies its own configuration.
In Sass, the equivalent module-wide configuration uses the with keyword. Jess
uses with for the more local form and reserves set for configuration that
should be reused by later imports in the same compilation.
set is a one-time choice per resolved module path. If it is used, it must be
used on the first import of that module: a later set, or a second set, is an
error.
Example:
// theme.jess
$primary-color: blue;
// file1.jess
@-compose './theme' set {
$primary-color: rebeccapurple;
}
.box {
color: $theme.primary-color; // rebeccapurple
}
// file2.jess
@-compose './theme';
.box {
color: $theme.primary-color; // rebeccapurple
}
Using the with keyword
with provides values only while the module is being composed into the current
file. It does not establish the configuration for later imports.
// file1.jess
@-compose './theme' with {
$primary-color: rebeccapurple;
}
.box {
color: $theme.primary-color; // rebeccapurple
}
// file2.jess
@-compose './theme';
.box {
color: $theme.primary-color; // blue
}
@-compose (reference)
This is the same as basic @-compose except rules will not be rendered unless extended. In other words, you can use reference to import values, variables, and functions.
This is much like Less's @import (reference), although with Less's @import (reference), the referenced file can see the parent's variables, but with @-compose (reference), the scope is isolated.
// rules.jess
$border-color: green;
.box {
border: 1px solid $border-color;
}
@-compose (reference) './rules';
.container {
border: 2px dashed $rules.border-color;
}
This would output:
.container {
border: 2px dashed green;
}
@-compose (mutable)
Composed modules are protected by default. Use mutable when a composition is
intended to allow downstream stylesheets to extend its selectors.
// library.jess
.box {
border: 1px solid green;
}
// main.jess
@-compose (mutable) './library.jess';
.second-box {
$extend library|.box;
}
This outputs:
.box,
.second-box {
border: 1px solid green;
}
Because library.jess is composed without an as name, its inferred namespace
is library. Mutability belongs to the composed module, not to the selector
name: another stylesheet can define its own .box and remain protected when
composed without mutable.
@-compose (export)
In Jess, variables defined or imported with @-compose (export) (or the namespace) will be re-exported and made available to a stylesheet downstream.
Use this sparingly! It may be harder to reason about where variables and values come from. Instead, consider importing stylesheet dependencies directly per file.
@-export (forwarding)
Sass has an at-rule called @forward which forwards a stylesheet API (variables, functions, and mixins) to downstream consumers.
In Jess, use:
@-export './stylesheet.jess';
This keeps forward intent explicit, avoids long option bundles, and reflects the current project direction.
@-use and @-from
We told you we had a good purpose for @-use, and here it is. Jess can import and use values from JavaScript, TypeScript, and JSON. Easily.
JSON imports are data imports and work without a script runtime. Built-in #less/* and #sass/* modules also stay inside Jess. To evaluate local or package JS/TS while compiling, install @jesscss/plugin-js; that path uses Deno because unknown JavaScript needs isolation.
The reason this is necessary is that, when maintaining Less, we found that it sometimes surprised developers to learn that their stylesheets could execute JavaScript, which meant potential security vulnerabilities if they weren't careful.
Jess supports two script-module forms. Use @-use when you want the Sass-module-style namespace form. Like Sass, you can omit as and Jess will infer the namespace from the module name:
@-use './my-module.js';
@-use './my-module.js' as js;
.box {
width: $my-module.width;
value: $js.myFunc();
}
Use @-from when you want the ESM-style import form. That includes named imports:
@-from './my-module.js' import (myFunc as fromJs);
.box {
value: $fromJs();
}
And namespace imports:
@-from './my-module.js' import * as js;
.box {
value: $js.myFunc();
}
You can also import and use Less and Sass functions.
@-use '#less/type';
@-use '#sass/math';
.box {
value: $(type.isnumber(math.e)); // true
}
@-import (discouraged)
Use of the @-import, by default, will trigger a deprecation warning when compiling.
For legacy compilation and migration support, Jess supports the @-import at rule (named @import in Less/Sass). However, the use of @-import is discouraged, for a few reasons.
- The
@-importrule has access to parent variables, so the "context" of evaluation changes for every import call, even of the same file. It also makes the imported file hard to reason about. - Variables from the root of the import are accessible anywhere in other stylesheets (they are re-exported), so it can be difficult to find the source file of a given variable or mixin.
- It intentionally overlaps the CSS
@importrule, making it unclear from code whether the@importshould appear in rendered output.
@-import './my-file.jess';