Migrating from sass-rails to Dart Sass

Migrating from sass-rails to Dart Sass

For those who have been coding CSS since the Internet Explorer 6 era, when aligning divs on the web was an art, and there was no way to use partials or variables, the arrival of SCSS was a gift to life. At that moment, creating partials and reusing variables for your primary colors was a delightful experience.

Now in 2026, for the real fans, it’s possible you’re still using sass-rails and you’re full of these files around your project. So if you still want to keep using it like me, I recommend migrating to Dart Sass, which is, in fact, a simple migration, to avoid headaches in the future and, most importantly, to start using the latest features.

Historical context

To give a bit of context, you may remember that sass-rails has been the default SCSS library since Rails 3.1 (2011). Some engineers like me were using Sass before that, but that’s when it became a Rails standard.

Behind it, sass-rails originally used the Ruby implementation of Sass (the original sass gem). That implementation reached its end-of-life in 2019, so starting with Rails 6 the default moved to sassc-rails, which used LibSass, the C/C++ port of the Sass compiler. LibSass was built to be a faster and more portable alternative to the original Ruby-based compiler.

However, LibSass reached its official end-of-life opens a new window (EOL) in October 2025 too. So now the recommendation is to migrate to Dart Sass, which includes more features and better CSS compatibility. As the name says, it’s built in the Dart language, and the wrapper tool uses the standalone Dart Sass executable, meaning you don’t even need Node.js installed to use it.

Why keep using Dart Sass?

A fair question in 2026 is why bother with Sass at all when modern CSS already has native variables, nesting, and more. The honest answer: if your project already runs on SCSS, a full rewrite to plain CSS is a big effort with little payoff.

Dart Sass is also the only Sass implementation still maintained. It’s the reference now, it gets all the new features first, and it keeps the same partials, mixins, and functions you already use. So instead of throwing away years of styles, you keep them and stay on a tool that’s still alive.

What Changes in Dart Sass

These are the most important changes.

/ division operator deprecated (biggest breaking change)

Maybe you never noticed, but the slash / operator has two behaviors: on one hand, it works as division, but in another context, it works as a separator. You can imagine the migraine for the devs maintaining that.

$container-width: 1200px;
$grid-columns: 12;

.sidebar {
  // as divisor
  width: $container-width / $grid-columns;

  // as separator
  font: 16px / 1.5 sans-serif;
}

For division, you now use math.div():

@use 'sass:math';

.sidebar {
  width: math.div($container-width, $grid-columns);
}

And when you actually want a slash separated list with variables, you use list.slash():

.sidebar {
  font: list.slash($font-size, $line-height) sans-serif;
}

@import deprecated in favor of @use and @forward

You probably also didn’t notice that when you do an @import foo on a file, all the imported values are loaded into the global namespace. That means, for example, that all the functions become available globally. If you’re experienced enough (and if not, just trust us), having everything available, especially on medium or big projects, can soon become a mess of cascading rules overwriting each other. It makes compilation slow as a consequence of importing the same file multiple times, and it increases the complexity of knowing where a variable or function came from.

The good news is that for you it just means starting to replace @import foo with @use foo.

Imagine you have one file where you define the variables:

// _variables.scss
$brand-color: #ff0000;

On the other side, you have a file with a few functions, but it also uses the variables:

// _mixins.scss
@import "variables";

@mixin brand-border {
  border: 2px solid $brand-color;
}

Here, brand-border is available globally. In your file you’d use it by doing:

@import "variables";
@import "mixins"; // Works because everything is globally leaked

.button {
  color: $brand-color;
  @include brand-border;
}

Now, with the latest version, the code looks similar:

@use "variables"; // Load variables for main.scss
@use "mixins";    // Load mixins for main.scss

.button {
  color: variables.$brand-color; // Uses variables namespace
  @include mixins.brand-border;   // Uses mixins namespace
}

Now it’s using namespaces and nothing is globally available.

Other breaking changes

Finally, there’s a list of more breaking changes here opens a new window , but if you’re working on a Rails project, the most typical ones are:

Math functions now require an explicit module import:

// Before
$value: round(1.5);
$half: floor(10px / 2);

// After
@use 'sass:math';

$value: math.round(1.5);
$half: math.floor(math.div(10px, 2));

Color functions are deprecated in favor of the sass:color module:

// Before
$dark: darken(#036, 10%);
$light: lighten(#036, 10%);

// After
@use 'sass:color';

$dark: color.adjust(#036, $lightness: -10%);
$light: color.adjust(#036, $lightness: 10%);

Other modules like sass:string, sass:list, and sass:map follow the same pattern. Also, global functions like str-length(), map-get(), and map-keys() now require an explicit @use declaration.

Using the Migrator

Luckily there’s a Sass Migrator opens a new window that automatically updates your files to make it easier to migrate your project.

You can install it with npm install -g sass-migrator. After that, if your base file is application.scss, you can run it like this:

sass-migrator module --migrate-deps app/assets/stylesheets/application.scss

There are a few options there. The first useful one is --migrate-deps, which tells the migrator to update both the explicitly specified stylesheets and any dependencies linked via @use, @forward, or @import rules.

A few Rails tips

Once you switch to the dartsass-rails gem, a couple of things saved us headaches.

First, register any extra SCSS entry point in config/initializers/dartsass.rb. The gem builds application.scss by default, but any other entry point you have (an admin stylesheet, a separate theme, etc.) won’t be compiled by Dart Sass unless you add it here. If you forget, those files won’t be compiled into CSS at all, or Sprockets will throw an error since it no longer has a Sass compiler to process them:

Rails.application.config.dartsass.builds.merge!(
  "admin.scss" => "admin.css",
  "theme.scss" => "theme.css"
)

Second, third-party files you don’t control will still throw deprecation warnings. Use --quiet-deps to silence the noise from those while keeping warnings for your own code:

Rails.application.config.dartsass.build_options << "--quiet-deps"

And once your own code is clean, turn the deprecations you already fixed into build errors so they don’t creep back in:

Rails.application.config.dartsass.build_options << "--fatal-deprecation=import"
Rails.application.config.dartsass.build_options << "--fatal-deprecation=mixed-decls"

Conclusion

Migrating from Sass Rails to Dart Sass isn’t as scary as it sounds. The Migrator does most of the heavy lifting, and the rest is mostly swapping @import for @use and adding a few module imports. Do it now while it’s still a small migration, and you get to keep your SCSS files and stay on a tool that’s actually maintained.

Planning a bigger Rails upgrade and want a hand? We can help you get there! opens a new window

Get the book