Sass @use vs @import: How to Migrate Off Deprecated Imports

@use vs @import: The Short Answer
@use is the modern way to load one Sass file from another, and @import is the deprecated way. The practical difference: @use loads each file exactly once and gives its variables, mixins, and functions a namespace, while @import dumps everything into one global scope and re-compiles a file every time it's imported. Dart Sass has emitted deprecation warnings for @import since version 1.80.0 (released in October 2024), and the rule is scheduled for removal in Dart Sass 3.0.
If you're starting a new project today, use @use and @forward exclusively. If you're maintaining an older codebase full of @import statements, the migration is more mechanical than scary — and Sass ships an official tool that does most of it for you. This guide covers both.
If you're newer to Sass itself, start with what Sass is and why it exists or our complete guide to Sass; this article assumes you already write SCSS and want to modernize how your files talk to each other.
Why Sass Deprecated @import
@import looks harmless, and for small projects it mostly is. At scale, it causes four recurring problems that the Sass team spent years watching developers fight.
Everything lands in one global scope
When you write @import 'buttons';, every variable, mixin, and function in _buttons.scss becomes globally available — along with everything that file imported, and so on down the chain. In a large codebase you end up with hundreds of globals and no way to tell where $accent-color was actually defined. Two partials that both define $spacing silently overwrite each other based on import order.
Files get compiled more than once
@import is textual inclusion, like copy-pasting the file's contents in place. If _buttons.scss and _forms.scss both import _mixins.scss, and your main file imports both, any CSS rules inside _mixins.scss are emitted twice in your compiled output. Teams worked around this with mixin-only partials and careful conventions, but the language itself never protected you.
No privacy
With @import, a library can't have internal helpers. Every -private-function you write is technically callable by anyone who imports your file. The convention of prefixing internals with a dash or underscore was just that — a convention.
It collides with CSS's own @import
Plain CSS has its own @import rule for loading stylesheets over the network. Sass had to guess which one you meant based on file extensions and URL patterns. @use removes the ambiguity entirely: it's unambiguously a Sass feature, and @import can eventually go back to meaning exactly what it means in CSS.
How @use Works
@use loads another Sass file as a module. The file is compiled once, its CSS is included once, and its members are exposed under a namespace.
// _corners.scss
$radius: 3px;
@mixin rounded {
border-radius: $radius;
}
// buttons.scss
@use 'corners';
.button {
@include corners.rounded;
padding: 5px + corners.$radius;
}
Compiled output:
.button {
border-radius: 3px;
padding: 8px;
}
Two things to notice. First, you reference members through the namespace: corners.rounded, corners.$radius. Second, the namespace defaults to the last component of the URL, minus any leading underscore and file extension — @use 'src/corners' still gives you corners.
One structural rule: @use rules have to come before any style rules in the file. They aren't required to be the very first lines, though — @charset, @forward, and variable declarations may precede them, which is how you define a value locally and then pass it into a module's configuration. What you can't do is conditionally @use something halfway down a stylesheet the way people sometimes did with @import.
Choosing a namespace (or none)
You can rename the namespace with as, which is handy for long file names:
@use 'typography-helpers' as type;
h1 {
@include type.heading(2rem);
}
And you can drop the namespace entirely with as *:
@use 'corners' as *;
.card {
@include rounded;
}
Use as * sparingly — it gives up the collision safety you migrated for. It's reasonable for a single core config file that everything depends on; it's a bad habit as a default.
Configuring modules with with
@use ... with replaces the old pattern of redefining variables before an @import. It only works on variables the module marks as configurable with !default:
// _theme.scss
$primary: #036 !default;
$radius: 4px !default;
.badge {
background: $primary;
border-radius: $radius;
}
// site.scss
@use 'theme' with (
$primary: #d81b60
);
The compiled .badge rule uses #d81b60 for its background and keeps the default 4px radius. A module can only be configured the first time it's loaded, which is another reason to centralize configuration in one entrypoint file.
Private members
Prefix a member with - or _ and it becomes genuinely private — invisible outside its own file:
// _helpers.scss
$-base-unit: 8px; // not accessible from other files
@function spacing($multiplier) {
@return $-base-unit * $multiplier;
}
Built-in modules
The module system also reorganized Sass's built-in functions. Instead of a flat global list, they live in modules like sass:math, sass:color, sass:string, and sass:list:
@use 'sass:math';
@use 'sass:color';
.col {
width: math.div(4, 12) * 100%;
border-color: color.adjust(#036, $lightness: 20%);
}
This matters for migration because the old global functions are being phased out too. darken() and lighten() are discouraged in favor of color.adjust() or color.scale(), and slash division ($width / 3) has been replaced by math.div(). Recent Dart Sass versions emit deprecation warnings for the global versions of most built-in functions, so a full modernization pass usually touches these at the same time.
@forward: The Other Half of the Module System
@use loads a module for your own use. @forward re-exports a module's members so that files loading your file can see them. You need it any time you split a library across multiple partials but want consumers to load a single entrypoint.
Say your library lives in a folder called library/:
// library/_variables.scss
$primary: #036 !default;
// library/_mixins.scss
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
// library/_index.scss
@forward 'variables';
@forward 'mixins';
The URLs in _index.scss are resolved relative to _index.scss itself, so @forward 'variables' picks up library/_variables.scss — no folder prefix needed. Now a consumer loads the whole library through one path:
// site.scss
@use 'library';
.cta {
background: library.$primary;
}
Compiled output:
.cta {
background: #036;
}
Note the folder name, not a file name: when you @use a directory, Sass automatically loads the _index.scss (or _index.sass) inside it, and the namespace comes from the directory — library. That combination — a folder of focused partials plus an _index.scss full of @forward rules — is the standard way to structure a modern Sass library or design system.
@forward also supports visibility control (show and hide) and prefixing (as btn-*), which lets a library expose a curated public API instead of everything it happens to contain. Importantly, @forward does not make members available in the forwarding file itself; if _index.scss also needs $primary, it must @use the file too.
@use vs @import at a Glance
| Behavior | @import |
@use |
|---|---|---|
| Status | Deprecated (warnings since Dart Sass 1.80.0) | Current standard |
| Scope | Everything global | Namespaced per module |
| File loaded | Every time it's imported | Exactly once per compilation |
| Duplicate CSS output | Possible | Prevented |
| Private members | Convention only | Enforced with -/_ prefix |
| Configuration | Redefine variables before import | Explicit with (...) syntax |
| Placement | Anywhere in the file | Before any style rules |
| Re-exporting | Automatic (everything leaks through) | Explicit via @forward |
How to Migrate from @import to @use
Step 1: Confirm you're on Dart Sass
@use is only implemented in Dart Sass — LibSass (the engine behind the old node-sass package) was deprecated in 2020 and never supported the module system. If your build still uses node-sass, switch to the sass package first. Our guide to installing and compiling Sass walks through the setup.
Step 2: Run the official migrator
The Sass team maintains a migration tool that rewrites @import rules, adds namespaces to every reference, and converts global built-in functions to their module equivalents:
npm install -g sass-migrator
sass-migrator module --migrate-deps your-entrypoint.scss
The --migrate-deps flag tells it to follow and migrate every file your entrypoint loads, so one command typically converts an entire project. Commit your work first, run it, and review the diff.
The module migrator handles the module system itself. Slash division is a separate migration with its own command, so if you still have $a / $b anywhere, run that pass too:
sass-migrator division --migrate-deps your-entrypoint.scss
That one rewrites division expressions to math.div() and adds the @use 'sass:math' rule where it's needed. Running module alone will not silence slash-division deprecation warnings.
Step 3: Clean up what the tool can't decide
The migrator is good, but a few judgment calls remain yours:
- Collapse shared globals into a config module. If dozens of files used the same imported variables, consider a single
_config.scssthat partials@useexplicitly, so dependencies are visible. - Add
@forwardentrypoints. Group related partials into folders with an_index.scss, so consumers load one path instead of six. - Audit any
as *output. The migrator sometimes preserves global-style access where namespacing would break things; tighten these up over time. - Mark intended-private members with a leading
-so the compiler enforces what used to be convention.
Step 4: Handle third-party libraries
Libraries written for @import can still be loaded with @use — namespacing works on any Sass file. Configuration is the sticking point: older libraries that expect you to define variables before importing them need the with (...) syntax instead, which works as long as the library declared its variables with !default. Bootstrap (which has been Sass-based since v4) and most maintained libraries document a module-system approach; check for an updated integration guide before hand-rolling one.
Common Gotchas
"Undefined variable" after migrating. Almost always a missing namespace — $primary needs to become theme.$primary, or the file needs its own @use statement. Under @use, every file explicitly loads what it depends on; nothing arrives ambiently anymore. The quickest diagnosis is to search the project for the variable's declaration and add a @use for that partial at the top of the failing file. Watch for privacy too: if a helper picked up a leading - or _ during cleanup, it's now invisible outside its own file, so cross-partial callers need it forwarded deliberately or renamed back.
Namespace collisions between same-named partials. @use 'buttons' and @use 'admin/buttons' both default to the namespace buttons, and Sass refuses to compile a file that loads both. Rename one at the call site with @use 'admin/buttons' as admin-buttons, or route both through a single @forward entrypoint that applies an as prefix to one of them.
A module was "already loaded" and can't be configured. with only works the first time a module is loaded in a compilation, and "first" means first in load order — not first in the file you consider most important. If a partial buried deep in the tree does a plain @use 'theme' before your entrypoint reaches @use 'theme' with (...), the compile fails. The durable fix is to funnel configuration through one file: a _configured-theme.scss that does the configured @use and then @forwards the module, with every other file loading that instead.
Duplicate CSS disappeared — and that changed the cascade. If your old build emitted a partial's rules twice, the later copy was winning on source order; the two copies had identical specificity, so whichever came last applied. Deduplication is exactly what you migrated for, but it can expose a rule that was quietly being overridden by its own twin. Diff your compiled CSS before and after, and look closely anywhere a selector's declarations moved earlier in the output.
Indented-syntax projects work identically. Everything here applies to both SCSS and the indented syntax — see Sass vs SCSS if you're weighing the two.
The Bottom Line
@use and @forward fix real problems: global-namespace collisions, duplicated output, and invisible dependencies. @import still compiles today, but it prints warnings now and has a removal date on the calendar, so every month of delay makes the eventual migration slightly larger. Run sass-migrator on a branch this week — for most projects the whole exercise, including review, fits in an afternoon, and your stylesheets come out easier to reason about than they went in.