Configuring Modules with @use ... with
Ship configurable libraries: !default variables, @use with (), and forwarding configuration downstream.
The module system turns a stylesheet into a configurable package. A module declares its knobs as !default variables; a consumer overrides them exactly once, at load time, with @use ... with (...).
The contract
- Only variables marked
!defaultcan be configured. - A module can be configured once per compilation — the first
@use ... withwins, so configure at the top of your entry file. @forward 'lib' with (...)lets an index file pre-configure a dependency it re-exports.
// _config.scss
$primary: #CC6699 !default;
$radius: 4px !default;
// main.scss
@use 'config' with ($primary: #6699CC);
// $radius keeps its default, $primary is overriddenThis is the clean replacement for the old 'set a variable before @import' hack. It is explicit, order-independent within the with block, and impossible to configure twice by accident.
Example
// ---- library/_config.scss ----------------------------------
$primary: #CC6699 !default;
$radius: 8px !default;
$font: system-ui, sans-serif !default;
// ---- library/_button.scss -----------------------------------
@use 'config';
.btn {
background: config.$primary;
border-radius: config.$radius;
font-family: config.$font;
}
// ---- library/_index.scss (public entry) ---------------------
@forward 'config'; // re-export the knobs
@forward 'button';
// ---- app/main.scss ------------------------------------------
@use 'library' with (
$primary: #6699CC,
$radius: 12px
);
// One block configures the whole package;
// $font falls back to its !default.When to use it
- A UI kit uses !default on every configurable variable so downstream projects only declare the variables they want to change via @use 'kit' with ().
- A responsive grid module exposes $columns and $gutter as !default variables, letting each project configure its own grid without forking the library.
- A developer forwards a configuration module with !default variables through an index file so parent project config flows down to all sub-modules automatically.
More examples
Library with !default config variables
The library's _config.scss declares all tokens as !default so consuming projects can override any of them when loading the module.
// lib/_config.scss
$primary: #2563eb !default;
$radius: 4px !default;
$font-sans: system-ui !default;
// lib/buttons.scss
@use 'config';
.btn {
background: config.$primary;
border-radius: config.$radius;
font-family: config.$font-sans;
}Consuming with @use ... with ()
Overrides all three config variables at the @use site; lib/buttons.scss automatically receives the project-specific values.
// project/main.scss
@use 'lib/config' with (
$primary: #e74c3c,
$radius: 0,
$font-sans: 'Roboto, sans-serif'
);
@use 'lib/buttons'; // picks up the configured valuesForwarding config to sub-modules
An index file @forward-s the config and component modules so a single @use ... with () on the index configures the entire library.
// lib/_index.scss
@forward 'config';
@forward 'buttons';
@forward 'forms';
// project/main.scss
@use 'lib' with (
$primary: #7c3aed
);
// $primary flows into config, buttons, and forms via @forward
Discussion