@import has been officially deprecated in Dart Sass since version 1.80.0, released in October 2024. The Sass team has committed to not shipping Dart Sass 3.0.0, the version that finally removes it, any sooner than two years after that, so not before October 2026. Ignoring the deprecation warning still buys you time. But not rewriting your import graph before 3.0.0 ships means your compiler starts treating @import as a literal CSS directive instead of a Sass one, and your build breaks. Fixing this is not a matter of a global find-and-replace to swap one keyword for another. It requires rethinking how your shared variables, mixins, and design tokens cascade through your stylesheet architecture.

  • Deprecation status: @import deprecated since Dart Sass 1.80.0 (October 2024).
  • Final removal target: Dart Sass 3.0.0, no sooner than two years after 1.80.0, so not before October 2026.
  • @use/@forward support: available since Dart Sass 1.23.0 (2019), so there is no reason to wait to start migrating.
  • Migration CLI: sass-migrator (npm install -g sass-migrator, then run with npx or the global binary).
  • Core logic: @use loads members into an isolated namespace. @forward exposes members to other files to build an API.
  • Bundler impact: Webpack and Vite users must update alias configuration to match the new module resolution paths.
  • On node-sass: this specific deprecation doesn't hit you, but don't read that as safety. node-sass wraps the older, already-deprecated LibSass engine and never implemented the module system at all. If you're still on it, you're carrying a bigger technical debt than this migration and should prioritize moving to Dart Sass first.

Why Is Sass @import Actually Being Deprecated?

The legacy @import rule was designed to mimic standard CSS, but it created a global scope problem for developers. Every time you imported a file, all of its variables, mixins, and functions became globally available to everything compiled after it. That made it nearly impossible to find where a specific $brand-primary variable was originally defined without searching through the entire codebase.

Library authors had to resort to awkward naming conventions, prefixing everything with long strings just to prevent collisions with the user's local code.

Performance took a hit too. If you used @import "buttons" in five different components, Sass compiled the button styles five separate times over, once per file that imported it, so a shared partial's output size effectively multiplies by however many files load it. Global scope leaks like this are the same class of problem behind other stubborn CSS bugs, like a box-shadow that silently refuses to render because an inherited rule further up the cascade overrides it.

The Real Consequence: What Happens If You Don't Migrate?

Many developers assume leaving the legacy code alone just means living with a console warning. The reality is more destructive. Dart Sass is moving toward treating @import exactly how standard CSS treats it: as a native browser directive to fetch an external stylesheet.

When Dart Sass 3.0 drops support for the Sass implementation of @import, the compiler stops pulling _variables.scss or _mixins.scss partials into your main bundle. Instead, it outputs a literal @import url('_variables.scss'); directly into your compiled CSS.

The browser then tries to make an HTTP request to fetch a .scss file that does not exist on your production server, and that import fails silently. Any variable, mixin, or function your code expected from that partial is now undefined, so those declarations get dropped as invalid CSS and your layout collapses wherever they mattered.

Advertisement

That's the part most migration guides bury in a footnote.

The Core Rule: When to Reach for @use vs @forward

The most confusing part of this migration is understanding the distinct roles of the two new rules. Most tutorials explain them in isolation, leaving developers guessing which one belongs in a given partial. The decision rule is straightforward once you frame it around the file's job.

Use @use for Application Logic

Reach for @use whenever a file needs to consume variables, mixins, or functions to apply actual CSS rules. It is for leaf files, the actual component stylesheets.

When you load a file with @use 'colors';, Sass automatically creates a namespace based on the file name. You access the variables like color: colors.$primary;. That keeps scope clean and localized to that specific component.

Rely on @forward for Shared Libraries

Reach for @forward when you are building an index file or a shared library block whose only purpose is to pass variables and mixins to other files. It acts as a routing mechanism.

If you have a folder full of design tokens, you don't want to force every component to @use fifteen different files. Instead, build a central _index.scss that forwards all the individual partials. Files loading that index get access to every forwarded member as if it were written in one document.

Step-by-Step Migration and Fixing Broken Builds

Running the Automated sass-migrator

The Sass team provides an official CLI to automate the heavy lifting (if npx throws version errors, check that your Node.js install is current first). Run it against your main entry point and it attempts to traverse the dependency tree:

Advertisement
npx sass-migrator module --migrate-deps src/styles/main.scss

The tool is strong at rewriting basic imports and adding namespaces, but it almost always chokes on complex legacy codebases, leaving a broken build that needs manual intervention.

Troubleshooting "Declaration Order" Errors

The most common error after running the migrator relates to declaration order. In the old system, you could drop an @import statement anywhere inside a file, even nested deep inside a selector.

The module system forbids that, and it fails loudly:

Error: @use rules must be written before any other rules.

Every @use and @forward rule must sit at the absolute top of the file, before any CSS rule, variable, or mixin is defined. If the migrator fails to extract a nested import, move it to the top manually and refactor the scoping logic around it.

Resolving Shared Variable Collisions

In the @import era, overriding a library's default variables was as simple as defining your variable first, then importing the library. The module system rejects that implicit override behavior and throws instead:

Error: This variable was not declared with !default in the @used module.

To configure a module, pass overrides explicitly with the with keyword. If the migrator output is not picking up your theme overrides, rewrite the import like this:

@use 'library' with (
  $primary-color: #ff0000,
  $border-radius: 4px
);

A Modern Architecture: Structuring Design Tokens with @forward

To get real value from the module system, restructure your design tokens using a centralized API pattern. Create a tokens directory holding the raw values: _colors.scss, _typography.scss, _spacing.scss.

Advertisement

Inside that directory, add an _index.scss file. This is where @forward earns its keep. Bundle all the tokens and prefix them to avoid namespace confusion when they get consumed later:

// tokens/_index.scss
@forward 'colors' as color-*;
@forward 'typography' as font-*;
@forward 'spacing' as space-*;

Now component files need a single @use statement. Because of the prefixes, accessing variables stays clean, semantic, and immune to naming collisions:

// components/_button.scss
@use '../tokens';

.btn {
  background-color: tokens.$color-primary;
  padding: tokens.$space-medium;
}

Handling Path Resolution in Bundlers

The shift from @import to @use often breaks path resolution in bundlers like Vite or Webpack. If you relied on includePaths in your Sass loader config to avoid long relative paths like ../../../styles/variables, that strategy needs an update.

Modern Dart Sass leans on the pkg: URL scheme and standard bundler aliases. Vite's built-in alias resolution works seamlessly with @use once vite.config.js maps the styling directory correctly:

resolve: {
  alias: {
    '@styles': path.resolve(__dirname, './src/styles')
  }
}

That lets you write @use '@styles/tokens'; from anywhere in the project tree, which removes path fragility for good and keeps the new module architecture robust. Once the migration is done, add a stylelint rule (at-rule-disallowed-list: ['import']) to your CI pipeline so nobody reintroduces a legacy @import by accident.