SASS/SCSS

@use and @forward: The Modern Module System

25 min Lesson 8 of 30

@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
Important: @use is the recommended way to load SASS modules. @import is deprecated and will be removed in SASS 3.0. All new projects should use @use.

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;
}
Namespace Syntax: To access members from a used module, use the syntax: namespace.member (e.g., colors.$primary, math.round(), mixins.flex-center())

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
Warning: Using "as *" removes the benefits of namespacing and can lead to naming conflicts. Use it only for modules like variables where you're confident there won't be conflicts, and you want cleaner syntax.

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;
}
Best Practice: Use private members for internal implementation details that shouldn't be used outside the module. This creates clear public APIs for your modules and prevents tight coupling.

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
!default Flag: Variables in the module must be defined with !default to be configurable. The !default flag means "use this value if the variable hasn't been set yet."

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
}
Migration Strategy:
  1. Start with leaf modules (those that don't import anything)
  2. Convert @import to @use, adding namespaces
  3. Update all references to use namespaced syntax
  4. Create index files with @forward for groups of modules
  5. Mark configurable variables with !default
  6. Make internal helpers private with - or _ prefix

Module System Best Practices

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