@use and @forward: The Modern Module System
@use and @forward: The Modern Module System
The @use and @forward directives represent SASS's modern module system, designed to replace the deprecated @import directive. This new system solves many of the problems with @import by introducing namespacing, preventing duplicate imports, and providing better control over what's exposed from each file.
Why @use Replaces @import
The @use directive was introduced to address fundamental problems with @import:
- Namespacing: @use automatically namespaces imported members, preventing naming conflicts
- No Duplicate Imports: A file is only loaded once, no matter how many times you use it
- Explicit Dependencies: You can see exactly where each variable/mixin comes from
- Private Members: Variables starting with - or _ are private to the module
- Better Performance: Files are only loaded once and cached
Basic @use Syntax
The @use directive loads another SASS file as a module. By default, members (variables, mixins, functions) are namespaced using the filename.
Simple @use Example
// _colors.scss
$primary: #3498db;
$secondary: #2ecc71;
$text: #333;
// main.scss
@use "colors";
body {
color: colors.$text;
background: colors.$primary;
}
h1 {
color: colors.$secondary;
}
// Compiled CSS
body {
color: #333;
background: #3498db;
}
h1 {
color: #2ecc71;
}
Accessing Module Members
When you @use a module, you access its variables, mixins, and functions through its namespace. The namespace is derived from the last component of the module's URL (the filename without the directory path and extension).
Accessing Different Member Types
// _variables.scss
$font-size: 16px;
$line-height: 1.6;
// _mixins.scss
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin button-variant($bg-color) {
background: $bg-color;
color: white;
&:hover {
background: darken($bg-color, 10%);
}
}
// _functions.scss
@function px-to-rem($px) {
@return $px / 16 * 1rem;
}
// main.scss
@use "variables";
@use "mixins";
@use "functions";
body {
font-size: variables.$font-size;
line-height: variables.$line-height;
}
.container {
@include mixins.flex-center;
padding: functions.px-to-rem(20);
}
.button {
@include mixins.button-variant(blue);
}
Custom Namespaces with "as"
If you don't like the default namespace, you can customize it using the "as" keyword. This is useful for shortening long module names or avoiding namespace conflicts.
Custom Namespace Example
// _typography-config.scss
$heading-font: "Georgia", serif;
$body-font: "Arial", sans-serif;
// main.scss - Default namespace
@use "typography-config";
body {
font-family: typography-config.$body-font;
}
// main.scss - Custom namespace
@use "typography-config" as typo;
body {
font-family: typo.$body-font;
}
// main.scss - Very short namespace
@use "typography-config" as t;
body {
font-family: t.$body-font;
}
The Special * Namespace (Use Sparingly)
You can use "as *" to load a module without a namespace, making its members available directly. However, this should be used sparingly as it defeats the purpose of namespacing.
Namespace-less Loading
// _variables.scss
$primary: blue;
$secondary: green;
// main.scss
@use "variables" as *;
// Now you can use variables directly without namespace
body {
color: $primary;
background: $secondary;
}
// This is similar to @import, but still has benefits:
// - File is only loaded once
// - Private members (starting with -) are still hidden
Private Members
In the @use module system, members (variables, mixins, functions) whose names start with either a hyphen (-) or an underscore (_) are considered private to the module. They cannot be accessed from outside the module.
Private Members Example
// _config.scss
// Public variable
$primary-color: blue;
// Private variable (starts with -)
$-internal-padding: 10px;
// Private variable (starts with _)
$_debug-mode: false;
// Private mixin
@mixin -helper-mixin {
padding: $-internal-padding;
}
// Public mixin that uses private members
@mixin component {
@include -helper-mixin;
color: $primary-color;
}
// main.scss
@use "config";
.element {
// This works - public variable
color: config.$primary-color;
// This works - public mixin
@include config.component;
// ERROR - private variable not accessible
// padding: config.$-internal-padding;
// ERROR - private mixin not accessible
// @include config.-helper-mixin;
}
Configuring Modules with "with"
One of the most powerful features of @use is the ability to configure a module before it's loaded. You can override variables defined with !default in the module.
Module Configuration
// _library.scss
$color: blue !default;
$size: 16px !default;
$border-radius: 4px !default;
.widget {
color: $color;
font-size: $size;
border-radius: $border-radius;
}
// main.scss
@use "library" with (
$color: red,
$size: 18px,
$border-radius: 8px
);
// The .widget class will now use:
// color: red
// font-size: 18px
// border-radius: 8px
Advanced Configuration Example
// _button-system.scss
$btn-bg: blue !default;
$btn-color: white !default;
$btn-padding: 10px 20px !default;
$btn-border-radius: 4px !default;
$btn-hover-darken: 10% !default;
@mixin button-base {
display: inline-block;
padding: $btn-padding;
background: $btn-bg;
color: $btn-color;
border: none;
border-radius: $btn-border-radius;
cursor: pointer;
&:hover {
background: darken($btn-bg, $btn-hover-darken);
}
}
// theme-1.scss
@use "button-system" with (
$btn-bg: #3498db,
$btn-border-radius: 20px
);
.button {
@include button-system.button-base;
}
// theme-2.scss
@use "button-system" with (
$btn-bg: #e74c3c,
$btn-color: white,
$btn-padding: 15px 30px,
$btn-hover-darken: 15%
);
.button {
@include button-system.button-base;
}
The @forward Directive
The @forward directive loads a module and makes its members available to files that load your file. This is useful for creating "index" files that re-export multiple modules, or for creating a public API for a library.
Basic @forward
// _colors.scss
$primary: blue;
$secondary: green;
// _typography.scss
$font-main: Arial, sans-serif;
$font-size: 16px;
// _index.scss (aggregates multiple files)
@forward "colors";
@forward "typography";
// main.scss
@use "index";
body {
color: index.$primary; // From colors
font-family: index.$font-main; // From typography
font-size: index.$font-size; // From typography
}
Controlling Visibility with show and hide
You can control which members are forwarded using the "show" and "hide" keywords. This lets you create selective exports and hide implementation details.
Using show
// _internal.scss
$public-var: red;
$internal-var: blue;
$debug-var: green;
@mixin public-mixin { }
@mixin internal-mixin { }
// _api.scss (only forward public members)
@forward "internal" show $public-var, public-mixin;
// main.scss
@use "api";
.element {
// This works
color: api.$public-var;
@include api.public-mixin;
// These don't work - not forwarded
// color: api.$internal-var;
// @include api.internal-mixin;
}
Using hide
// _config.scss
$primary: blue;
$secondary: green;
$internal-spacing: 8px;
$debug-mode: false;
// _public-api.scss (hide internal members)
@forward "config" hide $internal-spacing, $debug-mode;
// main.scss
@use "public-api";
.element {
// These work
color: public-api.$primary;
background: public-api.$secondary;
// These don't work - hidden
// padding: public-api.$internal-spacing;
// display: if(public-api.$debug-mode, block, none);
}
Adding Prefixes with as
When forwarding, you can add a prefix to all forwarded members. This is useful for creating namespaced collections of modules.
Prefix Example
// _buttons.scss
$bg-color: blue;
@mixin style { }
// _inputs.scss
$bg-color: white;
@mixin style { }
// _forms-index.scss
@forward "buttons" as btn-*;
@forward "inputs" as input-*;
// main.scss
@use "forms-index" as forms;
.button {
background: forms.$btn-bg-color; // From buttons
@include forms.btn-style;
}
.input {
background: forms.$input-bg-color; // From inputs
@include forms.input-style;
}
Building an Index File
A common pattern is to create an index file that forwards all related modules. This gives users a single entry point to your library.
Complete Index Pattern
// Directory structure:
// design-system/
// ├── _colors.scss
// ├── _typography.scss
// ├── _spacing.scss
// ├── _buttons.scss
// ├── _cards.scss
// └── _index.scss
// design-system/_colors.scss
$primary: #3498db !default;
$secondary: #2ecc71 !default;
// design-system/_typography.scss
$font-base: "Arial", sans-serif !default;
$font-size: 16px !default;
// design-system/_spacing.scss
$unit: 8px !default;
$small: $unit !default;
$medium: $unit * 2 !default;
$large: $unit * 3 !default;
// design-system/_buttons.scss
@use "colors";
@use "spacing";
.btn {
padding: spacing.$medium;
background: colors.$primary;
}
// design-system/_cards.scss
@use "colors";
@use "spacing";
.card {
padding: spacing.$large;
border: 1px solid colors.$primary;
}
// design-system/_index.scss
@forward "colors";
@forward "typography";
@forward "spacing";
@forward "buttons";
@forward "cards";
// main.scss in your project
@use "design-system" with (
$primary: #e74c3c,
$font-size: 18px,
$unit: 10px
);
// Now design-system.$primary, etc. are available
body {
font-size: design-system.$font-size;
color: design-system.$primary;
}
Migration from @import to @use/@forward
Migrating from @import to @use requires some changes to your code structure, but the benefits are worth it.
Before (with @import)
// _variables.scss
$primary: blue;
// _mixins.scss
@import "variables";
@mixin button {
background: $primary;
}
// main.scss
@import "variables";
@import "mixins";
.btn {
@include button;
color: $primary;
}
After (with @use)
// _variables.scss
$primary: blue;
// _mixins.scss
@use "variables";
@mixin button {
background: variables.$primary;
}
// main.scss
@use "variables";
@use "mixins";
.btn {
@include mixins.button;
color: variables.$primary;
}
// Or with "as *" for variables:
@use "variables" as *;
@use "mixins";
.btn {
@include mixins.button;
color: $primary; // No namespace needed
}
- Start with leaf modules (those that don't import anything)
- Convert @import to @use, adding namespaces
- Update all references to use namespaced syntax
- Create index files with @forward for groups of modules
- Mark configurable variables with !default
- Make internal helpers private with - or _ prefix
Module System Best Practices
- Use meaningful namespaces: Keep default names or use short, clear aliases
- Configure at the top: Use "with" clauses at the @use statement
- Mark everything configurable: Use !default on variables users might want to override
- Hide implementation details: Use - or _ prefix for private members
- Create index files: Use @forward to create single entry points
- Avoid "as *": Use explicit namespaces except for pure variable files
- Document your modules: Add comments explaining what's public and configurable
Well-Structured Module
// _button-system.scss
// Public API for button components
//
// Configurable variables:
// $background: Background color for buttons
// $text-color: Text color for buttons
// $padding: Internal padding
//
// Public mixins:
// button-base: Basic button styling
// button-variant($color): Create color variant
//
// Example:
// @use "button-system" with ($background: red);
// .btn { @include button-system.button-base; }
// Configuration (public, overridable)
$background: blue !default;
$text-color: white !default;
$padding: 10px 20px !default;
$border-radius: 4px !default;
// Private helpers
$-transition-speed: 0.3s;
@mixin -hover-effect {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
// Public API
@mixin button-base {
display: inline-block;
padding: $padding;
background: $background;
color: $text-color;
border-radius: $border-radius;
border: none;
cursor: pointer;
transition: all $-transition-speed;
&:hover {
@include -hover-effect;
}
}
@mixin button-variant($color) {
background: $color;
&:hover {
background: darken($color, 10%);
}
}
Exercise 1: Convert from @import to @use
Convert this @import-based code to use @use:
// _variables.scss
$primary: blue;
$font-size: 16px;
// _helpers.scss
@import "variables";
@mixin center { display: flex; justify-content: center; }
// main.scss
@import "variables";
@import "helpers";
body { font-size: $primary; @include center; }
Rewrite using @use with proper namespacing. Consider using "as *" for variables if appropriate.
Exercise 2: Create a Configurable Theme System
Build a theme system using @use and @forward:
- Create _theme.scss with color variables (all with !default)
- Create _components.scss that uses the theme colors
- Create _index.scss that forwards both files
- Create theme-light.scss and theme-dark.scss that configure the theme differently
Exercise 3: Build a Design System Index
Create a design system with multiple modules:
- _colors.scss, _typography.scss, _spacing.scss (configuration)
- _buttons.scss, _cards.scss, _forms.scss (components using configuration)
- _index.scss that forwards everything with appropriate prefixes
- Hide any internal/private variables
- Create main.scss that uses your design system with custom configuration