TH The Sass Way
Sass & SCSS

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

Sass @use vs @import: How to Migrate Off Deprecated Imports
tldr@use is the modern replacement for Sass's deprecated @import rule. @use loads each file once as a module and namespaces its variables, mixins, and functions, while @import loads files repeatedly into one global scope, causing name collisions and duplicated CSS. Dart Sass has warned on @import since version 1.80.0 (October 2024), with removal planned for Dart Sass 3.0. Migrate by running the official sass-migrator tool, then adopt @forward for library entrypoints.

@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:

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.

FAQ

Is @import deprecated in Sass?

Yes. Dart Sass has emitted deprecation warnings for @import since version 1.80.0, released in October 2024, and the Sass team plans to remove the rule in Dart Sass 3.0. Your existing stylesheets still compile today, but every @import prints a warning. New code should use @use and @forward instead, and existing projects should plan a migration rather than waiting for the removal release.

What is the difference between @use and @import in Sass?

@use loads a file once as a module and exposes its variables, mixins, and functions under a namespace, like buttons.$radius. @import is textual inclusion: it dumps everything into the global scope and re-includes a file every time it is imported, which can duplicate CSS output and cause silent name collisions. @use also adds real privacy via dash-prefixed members and explicit configuration with the with (...) syntax.

How do I migrate from @import to @use automatically?

Use the official migrator maintained by the Sass team. Install it with npm install -g sass-migrator, then run sass-migrator module --migrate-deps your-entrypoint.scss. The --migrate-deps flag follows every file your entrypoint loads, rewrites @import rules to @use, adds namespaces to references, and renames global built-in functions to their module equivalents, such as adjust-color() becoming color.adjust(). Slash division is handled by a separate command: run sass-migrator division --migrate-deps on the same entrypoint to convert $a / $b into math.div($a, $b). Commit first and review each diff afterward.

What does @forward do in Sass?

@forward re-exports another file's members so that stylesheets loading your file can use them. It is how you build a single entrypoint for a library split across many partials: a library/_index.scss containing @forward rules for each partial in that folder, which consumers then load with @use 'library'. It supports show and hide for visibility control and as prefix-* for renaming. Unlike @use, @forward does not make members available inside the forwarding file itself.

Can I still use @use with old Sass libraries built for @import?

Usually, yes. Namespacing works on any Sass file, so @use 'library' generally loads older libraries fine. The catch is configuration: instead of redefining variables before an @import, you pass overrides with @use 'library' with ($primary: red), which works as long as the library declared its variables with !default. Check the library's documentation first, since most maintained projects now publish module-system instructions.

Why am I getting undefined variable errors after switching to @use?

Because @use removes ambient globals. Every file must explicitly load what it depends on, and references need namespaces: $primary becomes theme.$primary after @use 'theme'. If a variable is genuinely missing, add a @use statement for the file that defines it at the top of the stylesheet. Also check for dash- or underscore-prefixed members, which are private under the module system and inaccessible from other files.