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
@useinstead of@importfor new code - Use
@forwardin index files to create public APIs - Prefix private members with
-or_for encapsulation - Use
@use...withfor module configuration - Prefer descriptive namespaces or aliases over
as * - Mark configurable variables with
!defaultflag - 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.