Adopting Sass @use for Component‑Based Styling: An Architecture Note
Switch from legacy Sass @import to @use to create scoped, component‑based styles. This architecture note covers requirements, minimal design, trust boundaries, operational checks, failure modes, and when to alter the design.
09 Nov 2025, 22:43 UTC

Problem & Takeaway
Legacy projects that rely on @import suffer from global variable leakage, name clashes, and brittle builds. Switching to @use gives each module its own namespace, making styling more maintainable. This note outlines the requirements, minimal design, trust boundaries, operational checks, failure modes, and when to reconsider the design.
Requirements
- Node.js 18+ or Ruby 3.1+ with Sass 1.56+ (Sass 3.5+ for
@use). - Build tool that can run
sass(Webpack, Vite, Gulp, or a plain CLI). - Linting support:
stylelintwithstylelint-scssplugin to enforce@useorder. - CI pipeline that compiles only changed modules and reports errors.
Minimal Design: Component‑Based Folder Layout
Organize the stylesheet tree so that each UI component owns its own folder:
src/styles/
├─ components/
│ ├─ button/
│ │ ├─ _index.scss // public API
│ │ ├─ _button.scss // implementation
│ │ └─ _button.scss
│ └─ card/
│ ├─ _index.scss
│ └─ _card.scss
└─ utilities/
├─ _index.scss
├─ _colors.scss
└─ _spacing.scss
Each component’s _index.scss forwards only the public mixins, functions, or variables it wants to expose. The implementation file (_button.scss) @uses the shared utilities via a relative path.
@use vs @forward
@useimports a module and creates a namespace.@forwardre‑exports selected members from a module, keeping the original namespace.- Modules should
@useshared utilities, then@forwardthe public API in_index.scss.
Trust & Data Boundaries
Encapsulation is enforced by:
- Only exposing names through
@forwardin_index.scss. - Marking private files with a leading underscore and never importing them directly.
- Using
stylelint-scssruleno-duplicate-namesto prevent accidental re‑exports.
Example: _colors.scss defines $primary but does not forward it. Only _index.scss forwards $primary if it is part of the public API.
Operational Checks
- Linting
- Run
npx stylelint "src/styles/**/*.scss" --syntax scss. - Ensure
scss/at-use-no-unnamedandscss/at-use-orderare enabled. - Fail the build if any
@importremains.
- Run
- Compilation
- Compile only changed modules:
npx sass src/styles/components/button/_button.scss dist/button.css --no-source-map. - Check that the output contains no global variables (e.g.,
$primary). - Verify that the namespace is present:
button.$primaryis used in the compiled CSS.
- Compile only changed modules:
- CI Verification
- Run
npm testwhich triggers linting and compilation. - Use
--watchmode in development to catch errors early.
- Run
Failure Modes & Mitigation
- Renamed Variable – If a component
@uses$spacingbut the utilities module renames it to$gap, compilation fails. Mitigation: runstylelintwithscss/no-duplicate-namesto catch accidental overrides. - Unintended Export – If
_index.scssforwards a private variable, other modules can access it, breaking encapsulation. Mitigation: enforce a naming convention (e.g., underscore prefix for private) and lint withscss/no-duplicate-names. - Missing @use – A component forgets to
@use "../utilities/index". Build pipeline will emit an error; the CI will block merge. - Outdated Sass – Running
sass --versionbelow 3.5 will cause@useto fail. Mitigation: add a pre‑commit hook that checks Sass version.
When to Change the Design
- If the project must support legacy browsers that require
@importfor CSS Modules, revert to@importfor those modules. - When the build tool cannot process
@use(e.g., older Webpack loaders), upgrade or switch to a newer bundler. - If the team needs runtime theming (e.g., CSS variables generated at runtime), consider a hybrid approach: keep
@usefor static styles and usepostcssfor dynamic parts.
Concrete Example
Utilities module: src/styles/utilities/_index.scss
@use "sass:color";
$primary: #0066cc;
$secondary: #ff6600;
@forward "sass:color";
@forward "_colors";
@forward "_spacing";
Component implementation: src/styles/components/button/_button.scss
@use "../../utilities/index" as u;
.button {
background: u.$primary;
padding: u.$spacing-1;
}
Build command:
npx sass src/styles/components/button/_button.scss dist/button.css
Expected check: dist/button.css contains background:#0066cc and no global variable names.
Limitations & Practical Verification
- Requires Sass 1.56+; older environments will error on
@use. - Only static CSS can be modularized; dynamic values (e.g., CSS variables from JS) need a different strategy.
- Verify by inspecting the compiled CSS for namespace prefixes (e.g.,
button.$primary). - Run
npx stylelint "src/styles/**/*.scss"to confirm no@importremains.
Conclusion
Adopting @use with a component‑centric folder structure delivers clear namespaces, safer imports, and easier maintenance. By enforcing linting, strict forwarding, and CI checks, teams can avoid the common pitfalls of variable leakage and broken encapsulation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.