Sass @use and @forward: A Collision-Free Token API
Use Sass modules to namespace design tokens, expose a small public API with @forward, and configure themes without editing token source.
25 Jul 2026, 09:13 UTC

Two Sass files that both define $brand can overwrite one another when they share global scope. The practical fix is to make module loading explicit: use @use for namespaced access, and reserve @forward for a deliberate public API. The compiled CSS can stay the same while the source gains clearer ownership and fewer collision risks.
Choose a module boundary before choosing a namespace
A Sass module is a file loaded with @use. Dart Sass evaluates a module once per compilation and exposes its public variables, mixins, and functions through a namespace. By default, _tokens.scss becomes the tokens namespace. Renaming that file changes the namespace unless you specify one, such as @use './tokens' as design;.
Member names beginning with - or _ are private to the module that defines them. This is different from the underscore in _tokens.scss: the filename marks a partial file, while the member name controls access.
| Construct | Scope effect | Practical use |
|---|---|---|
@use | Loads a module and exposes members under its namespace. | Dependencies used directly by the current file. |
@forward | Re-exports selected members from another module. | A small, intentional public API or facade. |
@import | Adds members to the global scope. | Legacy code only; new imports are discouraged. |
Build a token facade that consumers can use safely
Suppose the project contains these files:
styles/
_tokens.scss
_mixins.scss
_index.scss
main.scss
Define configurable tokens in _tokens.scss. The !default flag is required for variables that consumers may later override:
$brand: #2457d6 !default;
$surface: #ffffff !default;
$space-3: 1rem !default;
$radius-card: 0.5rem !default;
A mixin can depend on those tokens without importing them into the consumer's scope:
@use './tokens';
@mixin card {
padding: tokens.$space-3;
border-radius: tokens.$radius-card;
background: tokens.$surface;
}
Next, make _index.scss the project's token entry point. The show clause limits the exported API; hide is the inverse when a module has many members.
@forward './tokens' show $brand, $surface, $space-3, $radius-card;
@forward './mixins';
Consumers now depend on one facade rather than the internal file layout:
@use './index';
.button {
color: index.$brand;
background: index.$surface;
padding: index.$space-3;
border-radius: index.$radius-card;
}
.panel {
@include index.card;
}
Place module-loading rules before style rules; Sass reports invalid ordering. If a consumer tries to read a private member, the compiler should reject it. That error is useful: it identifies an API boundary that needs an explicit public export rather than a variable rename.
Configure a theme variant without editing token source
The with clause supplies replacement values when a module is first loaded. A separate theme entry can configure and then forward the same token module:
@use './tokens' with (
$brand: #9a3412,
$surface: #fff7ed
);
@forward './tokens';
A consumer of that entry uses its namespace, for example warm.$brand. Use this as an alternative entry point, not alongside another load of the same tokens module with different settings. Sass cannot reconfigure a module that has already been loaded in that compilation.
This mechanism selects a build-time variant. If a product needs to change themes at runtime in the browser, emit CSS custom properties instead and treat Sass variables as the source values for those properties.
Verify the implementation and inspect the compiled CSS
This example assumes Dart Sass. LibSass is deprecated and does not implement the module system. Run the following from the project root in a development shell. The commands require read access to styles and write access to the output directory; they do not require elevated privileges.
cd <project-root>
sass --version
mkdir -p public
sass styles/main.scss public/main.css
grep -nE '\.(button|panel)' public/main.css
Check three results:
- The version command identifies Dart Sass. If it identifies LibSass, change the build tool before adopting
@useand@forward. - The compilation exits successfully, and the CSS search finds the
.buttonand.panelselectors. - The output contains ordinary CSS rather than namespace syntax such as
index.$brand. Module information exists only in the Sass source.
The output command overwrites its destination, so point it at a generated CSS file rather than a source file. If the project already manages public, use that build directory instead of creating another one.
Accept the trade-off: less global access, more explicit dependencies
Namespaces make references longer and reveal dependencies that global imports previously concealed. A broad @forward can also erase useful provenance, which is why the example exports a short list. Migrate one boundary at a time and compare compiled CSS before and after; differences often reveal code that was relying on an accidental global variable.
Do not bulk-replace every @import without checking those differences. The module system improves ownership, but it does not remove the need for a deliberate public API, version checks, or CSS comparison.
A practical starting point is one tokens module, one facade, and one consumer migration. Compile it, inspect the generated selectors, and only then move the next dependency behind the same boundary.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.