Stop Global @import Collisions: Migrate to Sass Modules with @use and @forward
Sass @import creates global name collisions across teams. @use and @forward introduce namespacing, private members, and a curated public API for large stylesheets. Here's how to migrate safely.
02 Aug 2025, 08:54 UTC

The collision problem with @import
In large Sass projects, two teams can independently add a $primary variable or a button mixin. With @import, both get dumped into the global namespace, and the last import wins. No one knows who owns the variable. This causes subtle bugs that surface late in the cycle. Dart Sass deprecated @import in 2019 (v1.23) and plans removal in Dart Sass 3.0, so the timeline pressure is real.
How @use and @forward solve it
@use loads a stylesheet as a module exactly once, exposing its members under a namespace. If _colors.scss defines $primary, you access it as colors.$primary after @use 'colors'. Accidental global-name collisions disappear.
Members beginning with - or _ are private to the module—real encapsulation for the first time in Sass. Configuration is explicit: only variables defined with !default can be overridden via the with clause at load time, so theming a library is deliberate, not a side effect of global override order.
@forward re-exports another module's members, letting a library present a single entry point. Authors curate the public API with show/hide and rename on re-export with as.
Worked example: a design-system entry point
Split a monolithic styles.scss into _tokens.scss, _mixins.scss, and _components/. An index.scss becomes the public API:
// index.scss
@forward 'tokens' hide _internal-spacing;
@forward 'mixins' as m-*;
@forward 'components/button';A consumer writes one line:
@use 'design-system' as ds;
.button {
color: ds.$primary;
@include ds.m-button-base;
}Verify namespacing by creating two partials that define the same variable name, loading both with @use, and confirming each is accessed via its namespace without collision.
Migration strategy for large codebases
The official sass-migrator automates much of the conversion, including namespace insertion. Run from the project root where source styles live:
sass-migrator module --dry-runReview the previewed diff for inserted @use statements, added namespaces, and moved imports. Critical constraint: @use rules must appear before any other rules in a file, so files that interleaved imports with declarations will need restructuring. The migrator handles simple cases; complex interleaving requires manual fixes.
Prerequisite: the build pipeline must already use Dart Sass. LibSass and node-sass never implemented @use and are deprecated.
Trade-offs and next steps
Because modules load once, patterns that relied on re-importing a file to re-emit CSS per theme break and need mixin-based alternatives. Nested @use inside selectors or media queries is not allowed. You cannot @use the same module twice with different with configurations—Sass raises an error, validating load-once semantics.
Actionable path: migrate a leaf library first, expose it via @forward, and require consumers to @use it with a namespace. Keep private helpers prefixed with _ and reserve !default variables for intentional configuration. That gives you collision-free growth without a big-bang rewrite.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.