How Sass @use and @forward Fix Theme Variable Chaos
Sass @use and @forward replace fragile @import globals with explicit namespaces and controlled public APIs. Learn how to structure theme tokens and avoid collisions.
25 Mar 2026, 09:03 UTC

If you have ever renamed a color variable in a shared Sass file and watched a dozen unrelated components break, you already understand the problem with @import. It copies every variable, mixin, and function into the global scope, so nothing is private and everything collides. Sass modules—available in Dart Sass, the primary implementation—replace that free-for-all with explicit namespaces and a controlled public API.
The global scope trap
With @import, a file like _variables.scss is textually included wherever it appears. If two libraries both define $spacing-unit, the last import wins. There is no way to ask “which $spacing-unit is this?” because there is only one global bucket. Renaming a token means touching every file that references it, and build order becomes a fragile contract.
@use creates isolated namespaces
@use loads a module once and exposes its members under a namespace. By default, that namespace is the filename: @use "theme"; gives you theme.$primary-color and theme.spacing(). Nothing leaks into the global scope unless you explicitly alias it with as *. Sass resolves these paths relative to the importing file, so moving a folder means updating the @use rules inside that folder rather than every consumer across the project.
@forward defines the public surface
Large projects often split theme logic across several partials—colors, typography, spacing—but consumers should not need to know that layout. @forward lets an index file re-export selected members:
// styles/_tokens.scss
@forward "theme/colors" show color, $colors;
@forward "theme/spacing" show spacing;
Now components can @use "tokens"; and access a stable API, while the underlying file structure remains free to change.
A worked example
The following example assumes Dart Sass running from your project root. Consider a small project with a theme module and a button component. The directory layout might look like this:
styles/
_theme.scss
_button.scss
main.scss
_theme.scss holds the raw data and a helper function:
$colors: (
"primary": #0066cc,
"danger": #cc3300,
);
@function color($name) {
@if not map-has-key($colors, $name) {
@error "Unknown color: #{$name}";
}
@return map-get($colors, $name);
}
_button.scss consumes it through a namespace:
@use "theme";
.button {
background-color: theme.color("primary");
border: 1px solid theme.color("danger");
}
main.scss is the entry point:
@use "button";
From the project root, compile with:
sass main.scss main.css
Expected result: main.css contains the button rules with the hex values resolved. If you change $colors in _theme.scss and recompile, the output updates accordingly.
Watching and linting
During development, run the Sass watcher from your terminal to recompile on save:
sass --watch main.scss:main.css
After refactoring to modules, run a linter such as stylelint with the Sass plugin to catch undefined variables or accidental namespace conflicts. The linter will flag references to variables that are no longer in the global scope, which is exactly the safety net you want during migration.
Trade-offs and limitations
Modules are stricter than @import, and that strictness has costs.
- No mixed syntax. A stylesheet cannot contain both
@importand@use. Sass will throw a compile error. Migrate a file completely before moving to the next. - Circular dependencies fail. If
_a.scssuses_b.scssand_b.scssuses_a.scss, compilation stops. Design your import graph as a directed acyclic graph—leaf modules first, composites last. - Singletons, not instances. Each module is evaluated once per compilation. You cannot load two different configurations of the same file under different namespaces in one build. If you need theme variants, generate separate entry points or use CSS custom properties for runtime switching.
Where to start
Pick a leaf module—usually a file of variables or functions with no dependencies—and convert it first. Replace @import "variables"; with @use "variables"; in the files that consume it, adding the namespace prefix to each reference. Once leaf modules are clean, introduce a @forward index so downstream code has a single import path. Compile after each step with sass main.scss main.css to confirm nothing broke before you continue.
The payoff is a codebase where renaming a token is a local change, not a global search-and-replace. That is the real argument for @use and @forward: they make the boundaries of your design system explicit.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.