Mert Özen Further With Every Line
Frontend Sass/SCSS — #07

Preparing for Dart Sass 2.0: Deprecations, --fatal-deprecation, Slash Division and the End of @import

Mert Özen Nov 14, 2026 12 min 13 views
Preparing for Dart Sass 2.0: Deprecations, --fatal-deprecation, Slash Division and the End of @import

In Dart Sass 2.0 most deprecations added up to 1.105.0 become errors (slash-div, color functions like darken(), the legacy JS API), while @import, global built-ins and the old if() stay warnings until 3.0. We get those errors today with --fatal-deprecation=dart-sass-2, clean up with sass-migrator, and show that Angular's hard-coded quietDeps hides deprecations in files loaded through includePaths from the build entirely.

Version note: The examples in this post were run with Dart Sass 1.108.2, Sass Migrator 2.7.0 and Angular 22.2 (TypeScript 6.0; the Sass 1.104.1 bundled with the Angular CLI). Dart Sass 2.0 had not been released when this post was written on 14 November 2026; the information about 2.0 is based on the Sass team's roadmap from September 2026 and on the deprecation list of 1.108.2 itself.

Guess First

The Angular client of Bookstore, the online bookstore used as the shared example across this site's series, has taken its first step towards Dart Sass 2.0: the slash-div deprecation has been made fatal in angular.json. In the shared abstracts folder someone wrote a function the old way, and the catalog component uses it:

// src/styles/abstracts/_breakpoints.scss
@function half($value) {
    @return $value / 2;
}

// src/app/app.scss
@use "abstracts" as *;

.book-list {
    row-gap: half(24px);
}

// angular.json
"stylePreprocessorOptions": {
  "includePaths": ["src/styles"],
  "sass": {
    "fatalDeprecations": ["slash-div"]
  }
}

Does ng build fail? Does it at least print a warning? The answer is in the Angular section of this post; for now, write down your guess.

In this post we look at the deprecations Dart Sass 1.x accumulated over seven years, which of them become errors in 2.0, and how to prepare today. The warnings we met one by one in earlier posts (slash-div, import, global-builtin, color-functions) come together here.

What Changed During Dart Sass 1.x?

Dart Sass 1.0 came out in March 2018, and the major version has not changed since. Within 1.x, though, the language changed considerably. Key milestones from the CHANGELOG:

  • 1.23.0 (2019): the module system, meaning @use, @forward and built-in modules such as sass:math. The subject of the fourth post of the series.
  • 1.77.7: declarations written after nested rules (mixed declarations) were deprecated; in 1.92.0 the behavior changed and Sass now emits them in source order, as native CSS nesting does.
  • 1.79.0: CSS Color Level 4 support (oklch(), lab(), new color spaces), RGB channels no longer rounded, old color functions such as darken() deprecated, and the legacy JS API starting to warn.
  • 1.80.0: @import and the global built-in functions (map-get(), percentage()) officially deprecated.
  • 1.95.0: support for CSS's new if() syntax; Sass's old if($condition, $a, $b) function deprecated.
  • 1.107.0: --fatal-deprecation gained the dart-sass-2 value.

One thing stands out in this list: most of the changes bring Sass closer to CSS. The slash being a separator rather than division, color functions matching CSS color spaces, the CSS syntax of if(). The Sass team's rule is that anything valid in CSS should mean the same thing in Sass.

What Will Change in Dart Sass 2.0?

The Sass team announced the plan in "The Road to Dart Sass 2" on 22 September 2026: most 1.x deprecations become errors in 2.0, while a few remain deprecations and are removed in Dart Sass 3.0. The target date is December 2026; as of 14 November 2026 the latest version on npm is 1.108.2 and there is no 2.0 prerelease. Rather than guessing which deprecations become errors in 2.0, I asked Sass itself; the 1.108.2 JavaScript API exposes this set as sass.deprecations.dartSass2. The set is every active deprecation added up to 1.105.0 minus four, and contains 22 deprecations. The ones that matter for Bookstore:

DeprecationExampleDart Sass 2.0Fix
slash-div$gap / 2Error; / becomes a separatormath.div() or calc()
color-functionsdarken($brand, 10%)Errorcolor.adjust() or color.scale()
legacy-js-apisass.renderSync()Errorsass.compile()
strict-unary$a -$bError$a - $b
import@import "tokens"Still a warning, removed in 3.0@use / @forward
global-builtinmap-get()Still a warning, removed in 3.0map.get()
if-functionif($a, 700, 400)Still a warning, removed in 3.0if(sass($a): 700; else: 400)

One detail to watch: darken() and map-get() are both global built-in functions, but their fates differ. Because darken() also falls under the color-functions deprecation, it fails in 2.0; map-get() keeps working until 3.0.

Getting the Errors Today with --fatal-deprecation

An old Bookstore file contains nearly the whole table:

@import "old-tokens";

.order-summary {
    // slash-div: becomes a separator in 2.0
    padding: $gap / 2;
    width: percentage(1 / $columns);
    // color-functions: an error in 2.0
    border-color: darken($brand, 10%);
    // global-builtin: still a warning in 2.0
    color: map-get((sale: #b91c1c), sale);
    // legacy if(): removed only in 3.0
    font-weight: if($columns > 3, 700, 400);
}

A normal compile prints eight warnings and produces CSS. To see today what Dart Sass 2.0 will say about this file, we pass --fatal-deprecation=dart-sass-2 to the CLI. A note: --fatal-deprecations (plural), which shows up often in blog posts, does not exist in the CLI; 1.108.2 stops with Could not find an option named "--fatal-deprecations". CLI flags are singular (--fatal-deprecation, --silence-deprecation, --future-deprecation); the JavaScript API options are plural (fatalDeprecations).

npx sass --fatal-deprecation=dart-sass-2 07/checkout.scss
DEPRECATION WARNING [import]: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
...
DEPRECATION WARNING [if-function]: The Sass if() syntax is deprecated in favor of the modern CSS syntax.

Suggestion: if(sass($columns > 3): 700; else: 400)
...
Error: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($gap, 2) or calc($gap / 2)

More info and automated migrator: https://sass-lang.com/d/slash-div

This is only an error because you've set the slash-div deprecation to be fatal.
Remove this setting if you need to keep using this feature.
  ╷
5 │     padding: $gap / 2;
  │              ^^^^^^^^
  ╵
  07/checkout.scss 5:14  root stylesheet

import and if-function stay warnings, while slash-div stops the compile. The dart-sass-2 value arrived in 1.107.0; with an older Sass you need the long list from the Sass blog: --fatal-deprecation=1.79.0,compile-string-relative-url,misplaced-rest,with-private,function-name,adjacent-compounds. Passing a version number means "everything deprecated in or before that version". 1.107.0 also added an exclusion syntax: --fatal-deprecation=dart-sass-2,import also makes the @import deprecation that stays until 3.0 fatal, while -import removes it from a set selected by version.

Cleaning Up with sass-migrator

The migrator has a separate command per deprecation. On a copy of the file I ran four of them in order:

npx sass-migrator division 07/migrated/checkout.scss
npx sass-migrator color 07/migrated/checkout.scss
npx sass-migrator if-function 07/migrated/checkout.scss
npx sass-migrator module --migrate-deps 07/migrated/checkout.scss
@use "sass:color";

@use "sass:math";

@use "sass:map";
@use "old-tokens";

.order-summary {
    // slash-div: becomes a separator in 2.0
    padding: old-tokens.$gap * 0.5;
    width: math.percentage(math.div(1, old-tokens.$columns));
    // color-functions: an error in 2.0
    border-color: color.adjust(old-tokens.$brand, $lightness: -10%, $space: hsl);
    // global-builtin: still a warning in 2.0
    color: map.get((sale: #b91c1c), sale);
    // legacy if(): removed only in 3.0
    font-weight: if(sass(old-tokens.$columns > 3): 700; else: 400);
}

The migrator's choices are interesting. It turned $gap / 2 into $gap * 0.5 rather than math.div(): dividing by two is the same as multiplying by a half, and easier to read. When it turned darken() into color.adjust(..., $space: hsl), it wrote $space explicitly, because the old function worked in HSL and that is no longer the default in the new API. The blank lines between the @use rules are cosmetic and can be tidied by hand. Then I compiled with the strictest setting and compared with the old output:

npx sass --fatal-deprecation=dart-sass-2,import,global-builtin,if-function 07/migrated/checkout.scss
.order-summary {
  padding: 6px;
  width: 25%;
  border-color: rgb(51.5406162465%, 23.7659508248%, 2.5770308123%);
  color: #b91c1c;
  font-weight: 700;
}

No warnings at all, and diff says the output is identical to the old file's. In a real project the order matters: running the small, mechanical migrators such as division and color first, rather than module, and comparing the output after each one, makes it easier to find which step caused a problem.

From the Legacy JS API to the Modern API

If you call Sass from your own build script (a Gulp task, a custom webpack loader, an old CLI tool), check whether it uses render()/renderSync(). I compiled the same file with both APIs:

// The same file through the legacy API (render) and the modern API (compile).
import * as sass from "sass";

const file = "07/migrated/checkout.scss";
// make every deprecation that becomes an error in Dart Sass 2.0 fatal today
const fatalDeprecations = [sass.deprecations.dartSass2];

const legacy = sass.renderSync({ file });
console.log(`legacy renderSync:         ${legacy.css.length} bytes`);

try {
    sass.renderSync({ file, fatalDeprecations });
} catch (error) {
    console.log(`legacy renderSync, fatal:  ${error.message.split("\n")[0]}`);
}

const modern = sass.compile(file, { fatalDeprecations });
console.log(`modern compile, fatal:     ${modern.css.length} bytes, ${modern.loadedUrls.length} files loaded`);
DEPRECATION WARNING [legacy-js-api]: The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.

More info: https://sass-lang.com/d/legacy-js-api

legacy renderSync:         154 bytes
legacy renderSync, fatal:  The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.
modern compile, fatal:     154 bytes, 2 files loaded

The legacy API itself is a deprecation; once the dartSass2 set is fatal, renderSync() fails without even looking at the stylesheet. The modern API produces the same CSS and also returns information such as loadedUrls that watch mode needs. Angular and Vite have already made this move: Angular 22.2's build code uses compileStringAsync(), and Vite 8 uses initAsyncCompiler() and compileStringAsync(). Projects using these tools have nothing to do; the problem usually turns up in an old helper script nobody has touched.

Third-party Libraries: quietDeps and silenceDeprecations

You cannot fix a deprecation inside a library. Sass offers two settings for this. --quiet-deps (quietDeps in the JS API) hides warnings coming from files loaded through a load path or an importer. --silence-deprecation (silenceDeprecations) silences a specific deprecation everywhere. I tried it with a file that loads an old grid library we do not maintain through a load path:

// an old grid library we do not maintain
$grid-gutter: 24px !default;

@mixin row {
    display: flex;
    margin-inline: -($grid-gutter / 2);
}
npx sass --load-path=07/vendor-lib 07/catalog.scss
npx sass --load-path=07/vendor-lib --quiet-deps 07/catalog.scss
npx sass --load-path=07/vendor-lib --quiet-deps --fatal-deprecation=dart-sass-2 07/catalog.scss

The first command shows the slash-div warning. The second hides it. The third is the important one: with --quiet-deps on, --fatal-deprecation=dart-sass-2 does not catch the library's slash-div either; the compile finishes without errors and produces margin-inline: -12px. So quietDeps disables not only the warning but also the fatal setting for those files. The library will break in Dart Sass 2.0, but you will only find out on the day you move to 2.0.

That is why the legitimate use of these settings is narrow: removing the noise of a library you have pinned, track and expect to be updated from the daily build. When you do that, you still need a separate check (weekly in CI, say) that compiles without quietDeps so you keep seeing the state of your libraries. Using silenceDeprecations to silence a deprecation in your own code is nothing more than postponing the debt.

In Angular: Its Own Sass Version and Hidden Warnings

In Angular these settings go under stylePreprocessorOptions.sass in angular.json: fatalDeprecations, silenceDeprecations and futureDeprecations. quietDeps is not on the list, because Angular's build code always passes it as a hard-coded true (quietDeps: true in the sass-language.js file of @angular/build).

The answer to the guess at the start of this post lies in that detail. ng build with fatalDeprecations: ["slash-div"] and the half() function in abstracts:

Application bundle generation complete.

No error and no warning. The reason is includePaths: Angular turns load paths into its own importer, every file loaded that way is a "dependency" as far as Sass is concerned, and quietDeps: true both silences them and exempts them from the fatal setting. Load the same function through a relative path such as @use "../styles/abstracts" and the build does what you expect:

✘ [ERROR] Error: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.
...
This is only an error because you've set the slash-div deprecation to be fatal.
...
  ../../../../src/styles/abstracts/_breakpoints.scss 19:13  half()

Bookstore's entire global style tree and every component's abstracts are loaded through includePaths. In other words, the Angular build never shows us the deprecations in most of our code. Going back to relative paths would bring back the problem we solved in the fourth post; instead, I separated the check from the build.

The second detail is the Sass version. Angular 22.2 compiles with its own Sass 1.104.1, and because the dart-sass-2 value arrived in 1.107.0 it does not recognize it:

▲ [WARNING] Invalid deprecation id or version "dart-sass-2". [plugin angular-sass]

The build does not stop; the value is silently ignored. In Angular 22.2 the right setting is the long list from the blog:

"sass": {
  "fatalDeprecations": [
    "1.79.0",
    "compile-string-relative-url",
    "misplaced-rest",
    "with-private",
    "function-name",
    "adjacent-compounds"
  ]
}

1.104.1 accepts this list without warnings. The dependencies of @angular/build@22.3.0-rc.0 on npm show Sass 1.108.2; once 22.3 is out, dart-sass-2 will work inside Angular too.

For the hidden warnings I added a small check script to bookstore-web that runs with the project's own Sass (sass@1.108.2, a dev dependency). It compiles every non-partial .scss file under src with the same load paths as Angular, without quietDeps, and with the dartSass2 set fatal:

npm run check:sass2
3 files, 0 failed, warnings: {}

When I put half() back, the same script catches what the Angular build missed:

FAIL src/app/app.scss
Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.
  styles/abstracts/_breakpoints.scss:19
3 files, 1 failed, warnings: {}

The script also compiles @angular/material through the node_modules load path; Material 22.2.7 produced no warnings.

In Production

Put the Sass 2.0 check in CI and keep it separate from the build. Because of Angular's quietDeps behavior, fatalDeprecations alone is not enough; a step like check:sass2 sees both your own code and your libraries. When Dart Sass 2.0 is released, bumping this script's Sass to 2.0 is the fastest way to see where you stand without waiting for Angular's own update.

Track the Sass version in two places. An Angular project has two Sass installs: the one @angular/build pins (1.104.1 in 22.2) and the one you install for the check. npm ls sass sass-embedded shows which is where. Since Sass changes along with Angular updates, comparing the compiled CSS in the update PR is the cheapest guard against the "potentially breaking bug fix" items from the first post.

Read the release notes when 2.0 ships. The 2.0 table in this post is based on the roadmap and on the dartSass2 set of 1.108.2; the blog post itself says the final word will be in the release notes. When 2.0 comes out in December, the list needs to be verified there.

Common Pitfalls

Silencing warnings with silenceDeprecations and forgetting about them. Every slash-div silenced today comes back as a build error on the day you move to Dart Sass 2.0, and all of them at once.

Setting fatalDeprecations in Angular and assuming the job is done. As you saw above, files loaded through includePaths fall outside this setting, and an unrecognized value such as dart-sass-2 is ignored with just a warning. After setting it, do not trust it until you have deliberately added a deprecation and seen the build actually fail.

Writing --fatal-deprecations on the CLI. The plural form is the JavaScript API's option name; on the CLI the flag is singular, and the error message says so plainly. In CI scripts this error is usually caught on the first run, but combined with || true it leaves a step that silently does nothing.

Running the migrators across the whole project at once and making a single commit. The module migrator can change hundreds of files; in the same commit as division and color, it becomes hard to find which step a regression came from. One commit per migrator, and a diff of the compiled CSS after each.

A Small Experiment

★☆☆ Compile 07/checkout.scss with --fatal-deprecation=1.79.0 and compare with the result you got with dart-sass-2. Is there a difference between the two settings for this file?

★★☆ Move 07/vendor-lib/_legacy-grid.scss under 07/ and load it from catalog.scss with a relative path. Why does the result change when you compile with --quiet-deps and --fatal-deprecation=dart-sass-2?

★★★ Extend the check:sass2 script to write a JSON report of how many deprecations of each kind there are and which files they come from, keep the report as a CI artifact, and fail the step if a new deprecation appeared between two reports.

Back to the question we asked at the start of the series, the answer is clearer now: for variables, nesting and color math native CSS is enough today; Sass's value lies in the module system, in generating rules and custom properties from data, in build-time errors, and in keeping a large style tree in order. The next posts in the series move to the everyday toolbox: the built-in modules one by one, responsive mixins and generating utilities, writing your own Sass library, and wiring Sass into the build. For the utility-first approach see the Tailwind CSS series, and for where Sass sits in the build chain the Build Tools series.