Modern Sass Module System (@use and @forward)

1. @use Directive for Module Import

Feature Syntax Behavior Notes
Basic Import NEW @use 'module'; Loads module with namespace Replaces @import in Dart Sass
Automatic Namespace Last path segment module.scss → module.$var Prevents naming conflicts
Access Members namespace.$variable Dot notation for access Variables, mixins, functions
Load Once Cached after first load Prevents duplicate CSS output Performance optimization
File Extensions Auto-resolves .scss/.sass No extension needed @use 'colors' finds colors.scss
Index Files @use 'folder' Loads folder/_index.scss Module aggregation
Built-in Modules @use 'sass:math' Standard library modules sass:color, sass:list, etc.
Scope File-scoped Not globally available Explicit imports required

Example: Basic @use directive

// _colors.scss
$primary: #007bff;
$secondary: #6c757d;

@mixin theme($color) {
  background: $color;
  color: white;
}

// main.scss
@use 'colors';

.button {
  background: colors.$primary;  // Access with namespace
  
  &:hover {
    background: darken(colors.$primary, 10%);
  }
}

.card {
  @include colors.theme(colors.$secondary);  // Mixin with namespace
}

// Built-in modules
@use 'sass:math';
@use 'sass:color';

.box {
  width: math.div(100%, 3);           // 33.333%
  padding: math.pow(2, 3) + px;        // 8px
  background: color.adjust(#036, $lightness: 20%);
}

// Index file loading
// styles/
//   _index.scss (exports other files)
//   _buttons.scss
//   _cards.scss

@use 'styles';  // Loads styles/_index.scss

.component {
  @include styles.button-base;
}

// Multiple modules
@use 'variables';
@use 'mixins';
@use 'functions';

.element {
  color: variables.$text-color;
  @include mixins.flex-center;
  width: functions.rem(24px);
}
Note: @use is the recommended way to load Sass modules. @import is deprecated and will be removed in future versions.

2. @forward Directive for Module Re-export

Feature Syntax Purpose Use Case
Basic Forward @forward 'module'; Re-exports module's members Create public API
Aggregate Modules Multiple @forward statements Combine modules into one Index/barrel files
Show/Hide @forward 'module' show $var; Selective re-export Curated APIs
Hide Members @forward 'module' hide $var; Exclude specific members Keep internals private
Prefix Members @forward 'module' as prefix-*; Add namespace prefix Avoid naming conflicts
Chain Forwarding Forward through multiple files Build module hierarchies Library architecture
CSS Output Includes forwarded CSS Styles bubble up Complete module output

Example: @forward for module organization

// File structure:
// styles/
//   _index.scss      (aggregator)
//   _colors.scss
//   _typography.scss
//   _buttons.scss

// styles/_colors.scss
$primary: #007bff;
$secondary: #6c757d;
$success: #28a745;

// styles/_typography.scss
$font-family: 'Arial', sans-serif;
$line-height: 1.5;

@mixin heading($level) {
  font-size: (4 - $level) * 0.5rem;
  font-weight: 600;
}

// styles/_buttons.scss
@mixin button-base {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

// styles/_index.scss (Barrel/Index file)
@forward 'colors';
@forward 'typography';
@forward 'buttons';

// Now users can import everything from one place:
// main.scss
@use 'styles';

.heading {
  color: styles.$primary;           // From colors
  @include styles.heading(1);       // From typography
}

.button {
  @include styles.button-base;      // From buttons
  background: styles.$success;       // From colors
}

// Selective forwarding with 'show'
// _public-api.scss
@forward 'internal' show $public-var, public-mixin;

// Only $public-var and public-mixin are accessible
// Other members from 'internal' remain hidden

// Selective forwarding with 'hide'
// _api.scss
@forward 'colors' hide $internal-color, $debug-color;

// All colors except $internal-color and $debug-color

// Adding prefixes
// _theme.scss
@forward 'light-theme' as light-*;
@forward 'dark-theme' as dark-*;

// Usage:
// @use 'theme';
// background: theme.$light-bg;
// color: theme.$dark-text;

// Combining show and prefix
@forward 'utilities' as util-* show $spacing, calculate-rem;

// Creates: util-$spacing, util-calculate-rem

// Library pattern with forwarding
// lib/
//   _index.scss
//   core/
//     _index.scss
//     _variables.scss
//     _mixins.scss
//   components/
//     _index.scss
//     _buttons.scss
//     _cards.scss

// lib/core/_index.scss
@forward 'variables';
@forward 'mixins';

// lib/components/_index.scss
@forward 'buttons';
@forward 'cards';

// lib/_index.scss
@forward 'core';
@forward 'components';

// User code:
@use 'lib';  // Gets everything through forwarding chain

3. Module Namespaces and Aliasing

Technique Syntax Result Use Case
Default Namespace @use 'colors'; colors.$primary Automatic from filename
Custom Alias @use 'colors' as c; c.$primary Shorter names
No Namespace @use 'colors' as *; $primary (global) Direct access (use carefully)
Long Path Names @use 'folder/subfolder/module'; module.$var Last segment becomes namespace
Conflict Resolution Different namespaces No conflicts Multiple modules with same names
Built-in Aliases @use 'sass:math' as m; m.floor() Convenient access

Example: Namespace and aliasing patterns

// Default namespace (filename-based)
@use 'styles/variables';

.component {
  color: variables.$primary;  // Full namespace
}

// Custom alias (shorter)
@use 'styles/variables' as vars;

.component {
  color: vars.$primary;       // Shorter alias
}

// Very short alias
@use 'styles/typography' as t;

.heading {
  @include t.heading(1);
  font-family: t.$font-family;
}

// No namespace (global access)
@use 'colors' as *;

.button {
  background: $primary;       // Direct access, no namespace
  color: $white;
}

// ⚠️ Use 'as *' carefully - can cause naming conflicts!

// Resolving conflicts with aliases
// Both modules have $primary
@use 'brand-colors' as brand;
@use 'ui-colors' as ui;

.header {
  background: brand.$primary;  // Brand primary
}

.button {
  background: ui.$primary;     // UI primary
}

// Long paths - last segment becomes namespace
@use 'styles/theme/dark/variables';

.dark-mode {
  background: variables.$bg;   // 'variables' from last segment
}

// Or use alias for clarity
@use 'styles/theme/dark/variables' as dark-vars;

.dark-mode {
  background: dark-vars.$bg;   // More descriptive
}

// Built-in module aliases
@use 'sass:math' as m;
@use 'sass:color' as c;
@use 'sass:list' as l;
@use 'sass:map' as map;  // Keep original name
@use 'sass:string' as str;

.calculations {
  width: m.div(100%, 3);
  color: c.adjust(#036, $lightness: 20%);
  padding: l.nth((5px, 10px, 15px), 2);
  font-size: str.unquote('"16px"');
}

// Multiple files with same names in different folders
@use 'components/buttons/styles' as button-styles;
@use 'components/cards/styles' as card-styles;

.btn {
  @include button-styles.base;
}

.card {
  @include card-styles.base;
}

// Combining with @forward and aliases
// _public-api.scss
@forward 'internal/colors' as color-*;
@forward 'internal/spacing' as space-*;

// Usage:
@use 'public-api' as api;

.element {
  color: api.$color-primary;
  margin: api.$space-md;
}
Warning: Using @use 'module' as *; removes namespacing and can cause naming conflicts. Use only when necessary.

4. Private Members and Module Encapsulation

Feature Syntax Visibility Purpose
Private Variables $-private-var or $_private-var Module-only Internal implementation
Private Mixins @mixin -private() { } Module-only Helper mixins
Private Functions @function -private() { } Module-only Internal calculations
Private Placeholders %-private Module-only Internal extends
Public Members No dash/underscore prefix Exported Public API
@forward Control show/hide directives Selective export Curated public API
Encapsulation Cannot access private from outside Enforced privacy Clean interfaces

Example: Private members and encapsulation

// _theme.scss
// Private members (internal use only)
$-base-size: 16px;
$_internal-ratio: 1.5;

@function -calculate-scale($level) {
  @return $-base-size * $_internal-ratio * $level;
}

@mixin -internal-reset {
  margin: 0;
  padding: 0;
}

%-internal-base {
  box-sizing: border-box;
}

// Public API
$primary-color: #007bff;
$secondary-color: #6c757d;

@function scale($level) {
  @return -calculate-scale($level);  // Uses private function
}

@mixin component-base {
  @include -internal-reset;          // Uses private mixin
  @extend %-internal-base;           // Uses private placeholder
  
  color: $primary-color;
}

// main.scss
@use 'theme';

.component {
  // ✅ Public members accessible
  color: theme.$primary-color;
  font-size: theme.scale(2);
  @include theme.component-base;
  
  // ❌ Private members NOT accessible
  // color: theme.$-base-size;        // ERROR
  // @include theme.-internal-reset;   // ERROR
}

// Library with public API
// _buttons.scss
// Private implementation details
$_default-padding: 10px 20px;
$_default-radius: 4px;

@mixin -set-colors($bg, $text) {
  background: $bg;
  color: $text;
  
  &:hover {
    background: darken($bg, 10%);
  }
}

// Public API
$button-variants: (
  primary: #007bff,
  secondary: #6c757d,
  success: #28a745
);

@mixin button($variant: primary) {
  padding: $_default-padding;
  border-radius: $_default-radius;
  border: none;
  cursor: pointer;
  
  $color: map-get($button-variants, $variant);
  @include -set-colors($color, white);
}

// Usage - only public members accessible
@use 'buttons';

.btn {
  @include buttons.button(primary);  // ✅ Works
  
  // ❌ Cannot access private
  // padding: buttons.$_default-padding;  // ERROR
}

// Selective public API with @forward
// _internal.scss
$-private-config: (debug: true);
$public-colors: (primary: blue);

@mixin -internal-helper { }
@mixin public-mixin { }

// _api.scss
@forward 'internal' show $public-colors, public-mixin;
// Hides $-private-config and -internal-helper

// Advanced: Configuration with private defaults
// _spacing.scss
$-default-base: 8px !default;  // Private default

// Public configuration variable
$base-spacing: $_default-base !default;

@function spacing($multiplier) {
  @return $base-spacing * $multiplier;
}

// Usage:
@use 'spacing' with ($base-spacing: 4px);  // Override public
// Cannot override $-default-base (private)

.element {
  margin: spacing.spacing(2);  // Uses configured base
}

// Module with clear public/private separation
// _grid.scss
// PRIVATE - Implementation details
$_max-columns: 12;
$_gutter: 16px;

@function -column-width($columns) {
  @return percentage($columns / $_max-columns);
}

// PUBLIC - API
@mixin container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 $_gutter;
}

@mixin row {
  display: flex;
  margin: 0 (-$_gutter);
}

@mixin col($size) {
  width: -column-width($size);
  padding: 0 $_gutter;
}

// Clear, documented public interface
// Users don't need to know about private implementation
Note: Private members (starting with - or _) cannot be accessed from other modules, ensuring clean encapsulation.

5. Module Configuration with @use...with

Feature Syntax Purpose Notes
Configuration @use 'module' with ($var: value); Override module defaults Module initialization
!default Flag Required in module Makes variable configurable $var: default !default;
Multiple Variables Comma-separated Configure many at once ($a: 1, $b: 2)
One-time Configuration First @use only Subsequent uses inherit Cached configuration
Cannot Reconfigure Once set, cannot change Consistent module state Prevents conflicts
Private Variables Cannot configure private Only public !default vars Encapsulation preserved
Forwarded Configuration Config flows through @forward Nested module config Deep configuration

Example: Module configuration patterns

// _theme.scss - Configurable theme module
// Default values (can be overridden)
$primary-color: #007bff !default;
$secondary-color: #6c757d !default;
$border-radius: 4px !default;
$spacing-unit: 8px !default;

// Derived values (based on configurables)
$primary-dark: darken($primary-color, 10%);
$primary-light: lighten($primary-color, 10%);

@mixin button {
  padding: $spacing-unit * 2;
  border-radius: $border-radius;
  background: $primary-color;
  
  &:hover {
    background: $primary-dark;
  }
}

// main.scss - Configure on import
@use 'theme' with (
  $primary-color: #e91e63,
  $secondary-color: #9c27b0,
  $border-radius: 8px,
  $spacing-unit: 4px
);

.custom-button {
  @include theme.button;  // Uses configured colors
  color: theme.$primary-dark;  // Uses derived value
}

// Multiple modules with different configs
// file1.scss
@use 'theme' with ($primary-color: red);

// file2.scss
@use 'theme' with ($primary-color: blue);  // Different config

// main.scss imports both
@use 'file1';  // Theme configured with red
@use 'file2';  // Theme configured with blue (separate instance)

// Advanced: Responsive grid configuration
// _grid.scss
$columns: 12 !default;
$gutter-width: 16px !default;
$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
) !default;

@function column-width($count) {
  @return percentage($count / $columns);
}

@mixin container($max-width: 1200px) {
  max-width: $max-width;
  margin: 0 auto;
  padding: 0 $gutter-width;
}

// app.scss - Custom grid configuration
@use 'grid' with (
  $columns: 16,
  $gutter-width: 24px,
  $breakpoints: (
    sm: 640px,
    md: 768px,
    lg: 1024px,
    xl: 1280px,
    xxl: 1536px
  )
);

.container {
  @include grid.container(1400px);
}

.col {
  width: grid.column-width(4);  // Uses configured 16-column grid
}

// Design system configuration
// _design-system.scss
$font-family-base: 'Arial', sans-serif !default;
$font-size-base: 16px !default;
$line-height-base: 1.5 !default;

$color-primary: #007bff !default;
$color-secondary: #6c757d !default;
$color-success: #28a745 !default;
$color-danger: #dc3545 !default;

$spacing-scale: (
  xs: 4px,
  sm: 8px,
  md: 16px,
  lg: 24px,
  xl: 32px
) !default;

// Brand A customization
@use 'design-system' with (
  $font-family-base: 'Roboto', sans-serif,
  $color-primary: #ff5722,
  $color-secondary: #795548,
  $spacing-scale: (
    xs: 2px,
    sm: 4px,
    md: 8px,
    lg: 16px,
    xl: 24px
  )
);

// Forwarding with configuration
// _base-theme.scss
$primary: blue !default;

// _extended-theme.scss
@forward 'base-theme';
$secondary: green !default;

// main.scss
@use 'extended-theme' with (
  $primary: red,      // Configures base-theme
  $secondary: purple  // Configures extended-theme
);

// Configuration validation pattern
// _validated-config.scss
$min-font-size: 12px !default;
$max-font-size: 24px !default;

@if $min-font-size >= $max-font-size {
  @error "min-font-size must be less than max-font-size";
}

@if $min-font-size < 10px {
  @warn "Font sizes below 10px may hurt accessibility";
}

// Complex configuration example
// _component-library.scss
$enable-shadows: true !default;
$enable-gradients: false !default;
$enable-transitions: true !default;

$transition-duration: 0.3s !default;
$shadow-size: 0 2px 4px rgba(0,0,0,0.1) !default;

@mixin card {
  padding: 1rem;
  border-radius: 4px;
  
  @if $enable-shadows {
    box-shadow: $shadow-size;
  }
  
  @if $enable-transitions {
    transition: all $transition-duration;
  }
  
  @if $enable-gradients {
    background: linear-gradient(to bottom, #fff, #f5f5f5);
  } @else {
    background: white;
  }
}

// Usage with feature flags
@use 'component-library' with (
  $enable-shadows: false,
  $enable-transitions: true,
  $enable-gradients: true,
  $transition-duration: 0.2s
);
Note: Variables must have !default flag to be configurable via @use...with. This ensures intentional configurability.

6. Migration from @import to @use

Aspect @import (Old) @use (New) Migration Step
Global Scope All imports global File-scoped with namespaces Add namespaces to references
Multiple Imports CSS duplicated each time Loaded once, cached Remove duplicate imports
Naming Conflicts Last import wins Namespaces prevent conflicts Resolve with aliases
Load Order Critical, fragile Dependency-based Review dependencies
Private Members No privacy - or _ prefix for private Mark internal members
Built-in Functions Global (floor, lighten) Module-based (math.floor) Add module prefix
Deprecation Will be removed Recommended approach Migrate before removal

Example: Migration guide and patterns

// BEFORE: Old @import style
// _variables.scss
$primary-color: #007bff;
$font-size: 16px;

// _mixins.scss
@mixin button {
  padding: 10px 20px;
}

// main.scss
@import 'variables';
@import 'mixins';
@import 'variables';  // ❌ Duplicated CSS

.button {
  color: $primary-color;      // Global access
  font-size: $font-size;      // Global access
  @include button;            // Global access
}

// AFTER: New @use style
// _variables.scss (same)
$primary-color: #007bff;
$font-size: 16px;

// _mixins.scss
// If needs variables, must import
@use 'variables';

@mixin button {
  padding: 10px 20px;
  color: variables.$primary-color;  // Namespaced
}

// main.scss
@use 'variables';
@use 'mixins';

.button {
  color: variables.$primary-color;   // Namespaced
  font-size: variables.$font-size;   // Namespaced
  @include mixins.button;            // Namespaced
}

// Migration Strategy 1: Gradual with 'as *'
// Phase 1: Switch to @use with global namespace
@use 'variables' as *;
@use 'mixins' as *;

.button {
  color: $primary-color;      // Still works (no namespace)
  @include button;            // Still works
}

// Phase 2: Add namespaces gradually
@use 'variables' as vars;
@use 'mixins' as mix;

.button {
  color: vars.$primary-color;
  @include mix.button;
}

// Migration Strategy 2: Built-in modules
// BEFORE
$width: floor(10.7px);
$color: lighten(#333, 20%);

// AFTER
@use 'sass:math';
@use 'sass:color';

$width: math.floor(10.7px);
$color: color.adjust(#333, $lightness: 20%);

// Common migration patterns
// Pattern 1: Global utilities to module
// BEFORE: _utilities.scss
@mixin clearfix { }
@mixin truncate { }
%flex-center { }

// AFTER: Keep same, just use differently
// _utilities.scss (no changes needed)
@mixin clearfix { }
@mixin truncate { }
%flex-center { }

// Usage changes:
// BEFORE
@import 'utilities';
@include clearfix;

// AFTER
@use 'utilities' as util;
@include util.clearfix;

// Pattern 2: Handling @import in modules
// BEFORE: _buttons.scss
@import 'variables';  // Gets variables globally

@mixin button {
  color: $primary;
}

// AFTER: _buttons.scss
@use 'variables';  // Explicit dependency

@mixin button {
  color: variables.$primary;  // Namespaced
}

// Pattern 3: Index files (barrel exports)
// BEFORE: styles/_index.scss
@import 'variables';
@import 'mixins';
@import 'functions';

// AFTER: styles/_index.scss
@forward 'variables';
@forward 'mixins';
@forward 'functions';

// Usage:
@use 'styles';  // Gets everything

// Pattern 4: Configurable libraries
// BEFORE: Global configuration
$theme-primary: red;
@import 'theme';

// AFTER: Module configuration
@use 'theme' with ($primary: red);

// Pattern 5: Dealing with third-party @import
// If library still uses @import, you can still @use it
// _third-party-lib.scss (uses @import internally)
@import 'some-old-file';

// your-file.scss
@use 'third-party-lib';  // Works, but shows deprecation warnings

// Pattern 6: Mixed migration (transitional)
// Use @import and @use together during migration
@import 'old-global-utils';     // Still using @import
@use 'new-module' as new;       // Migrated to @use

.component {
  @include old-mixin;           // From @import
  @include new.new-mixin;       // From @use
}

// Complete migration checklist:
// ☐ Replace @import with @use
// ☐ Add namespaces to all references
// ☐ Update built-in function calls
// ☐ Mark private members with - or _
// ☐ Use @forward for index files
// ☐ Add !default to configurable variables
// ☐ Remove duplicate imports
// ☐ Test thoroughly
// ☐ Update documentation

Module System Best Practices

  • Always use @use instead of @import for new code
  • Use @forward in index files to create public APIs
  • Prefix private members with - or _ for encapsulation
  • Use @use...with for module configuration
  • Prefer descriptive namespaces or aliases over as *
  • Mark configurable variables with !default flag
  • Use built-in modules with @use 'sass:math' etc.
  • Plan migration from @import incrementally for large codebases
Warning: @import is deprecated and will be removed in Dart Sass 2.0. Migrate to @use and @forward as soon as possible.