Understanding @use and @forward in Dart Sass

Table of contents

In recent years, CSS preprocessors have become very popular because they allow developers to write CSS programmatically. This has been a significant leap forward in flexibility and power when declaring styles. Sass is the most well-known and widely used among them. Although there are many other ways to style our web applications today—like CSS-in-JS in its many flavors, PostCSS, or CSS Modules—SASS is still widely used, as revealed by the latest The State of CSS 2021, and thus remains a relevant and powerful tool.

Module System

In 2019, Sass announced the Module System, a major core change that would phase out @import and some global functions. The Dart Sass implementation (JavaScript-based and in development since 2016) already supported this new module system, so the Sass organization started promoting its use over LibSass and RubySass.

Despite being backward compatible and allowing both approaches to coexist for a while, Sass planned to permanently drop support for @import and the old internal module system by October 1, 2022.

At that time, LibSass was still widely used (as a dependency of node-sass) but lacked full support for the new module system and was eventually deprecated, making the switch inevitable.

How does this affect me?

It depends. This means we’ll have a lot of legacy code that we’ll need to migrate, which you can do following this migration guide. But if something is working, why change it? Still, when using SASS in your new projects, you will need to use the new module system and thus understand the new rules.

@use and @forward

The most notable change in this version is the introduction of these two new directives, which replace @import and eliminate the global scope where utilities used to live.

Previously, we included all partial files in a single stylesheet (like an index) that styled other components or parts of the application, along with utilities like variables, mixins, placeholders, and functions. All partials had access to and could use those utilities. But with the new module system, when a partial wants to access a utility, it must import it explicitly, giving us more control and making it immediately clear what external code is being used and where it comes from—very similar to JavaScript imports.

And that’s not all. There are many other benefits.

Benefits

@use

@use 'theme';

.text {
	color: theme.$primary-color;
}

Declare it at the top of the file where you want access to members (variables, mixins, functions).

By default, these members are namespaced by the file name. So to access the $primary-color variable from theme.scss, you must use theme.$primary-color. This allows you to use multiple $primary-colors from different files in the same stylesheet.

You can also rename the namespace using as:

@use 'theme' as defaultTheme;
@use '../3rd-party-library/theme' as externalTheme;

.box {
	color: defaultTheme.$primary-color;
	background-color: externalTheme.$primary-color;
}

Or remove the namespace using *, although Sass will throw an error if any conflicts occur between variables from different imports:

@use 'theme' as *;
@use '../3rd-party-library/theme' as *;

.box {
	color: $primary-color; // from theme
	background-color: $primary-color; // from 3rd-party-library/theme
}

You can also make members private (i.e., not accessible from outside) by prefixing them with - or _:

// _palette.scss

$primary-color: '#282828'; // accessible variable

@mixin _reset-list {
	// private mixin, not accessible externally
	margin: 0;
	padding: 0;
	list-style: none;
}

@forward

Used to bring the contents of another file into a file—like an index importing partials via @import.

Like @use, it protects against code duplication if called multiple times. But unlike @use, @forward does not add a namespace.

// General settings
@forward 'base/reset';
@forward 'base/reset'; // no duplicate code
@forward 'base/common';

// Common components
@forward '../components/';

Like @use, you can manage member privacy with @forward, but in this case by using the show or hide clauses to control what gets exposed:

@forward 'hero' show $hero-color; // import ONLY $hero-color

@forward 'utils' hide triangle; // import EVERYTHING except triangle() mixin

Built-in modules

Some of Sass’s built-in modules and utilities have changed—they may be new, renamed, or removed. Be sure to check the official documentation. The key takeaway is that now you must explicitly access modules using @use, and you can rename them:

@use 'sass:math' as builtInSassMathModule;
@use 'sass:color';

@use 'theme';

body {
	&::before {
		content: builtInSassMathModule.random();
		color: color.alpha(theme.$color);
	}
}

Configuring third-party libraries

With @use, you can configure third-party libraries without overwriting their values by using with at the time of import:

$custom-breakpoints: (
	mobile: 375px,
	tablet-small: 640px,
	tablet: 780px,
	tablet-large: 920px,
	desktop: 1180px,
	wide: 1400px,
);

$custom-show-breakpoints: (mobile, tablet, desktop, wide);

@forward '../node_modules/sass-mq/mq' with (
	$breakpoints: $custom-breakpoints,
	$show-breakpoints: $custom-show-breakpoints
);

Conclusions

Even though the module system brought many benefits, there wasn’t a mass migration, and a lot of code still uses @import. This is likely due to several overlapping trends, such as:

Because of these growing options and the fact that LibSass was still available and didn’t force a change, it remained popular (as shown by npm trends). But with the announcement that LibSass is going away and the change will be mandatory for new development, if you want to keep using SASS, it’s time to embrace the change and stop delaying the inevitable.

That’s why we’ve shared a link to a basic scratch repo with some comments to help you get started. :)

This is the link to the repository for trying it out and play a little around.

Footnotes

In a previous article about the CSS functions min, max and clamp, we explained that to be able to use these functions in Sass we need to use them with quotation marks alongside the function unquote() because Sass understands that these names are referred to its own functions since they shared the same name.

.element-c {
	width: unquote("min(500px, 98%)");
}

.element-d {
	font-size: unquote("max(min(10%, ((1.25vw + 10px) * 0.5), 5vw), 1rem)");
}

Anyway, I you have already read the article pointed out at the beginning you could guess why now it is not a problem and why you can write as normal with dart-sass.

.element-c {
	width: min(500px, 98%);
}

.element-d {
	font-size: max(min(10%, ((1.25vw + 10px) * 0.5), 5vw), 1rem);
}

These functions belong to the ‘math’ built-in module, and since by default when importing a module with @use the members are encapsulated, there is no more collision between the CSS native function and the Sass one.


@use 'sass:math';

.element-c {
	width: min(500px, 98%); /* CSS native */
}

.element-e {
	width: math.min(500px, 300px); /* Sass function */
}
comments powered by Disqus

If you find it interesting

If you have any doubt or you want to chat about this topic, as if you find interesting the content or our profiles and you think we could build something together, do not hesitate to contact us trough the email address hola@mamutlove.com