Sass Mixins vs Functions: Architecture Notes for Design System Tokens
Separate Sass logic from output: use functions for values and mixins for CSS, behind the @use module system.
10 Jul 2026, 19:07 UTC

The problem: logic and output get mixed in one file
In a growing design system, Sass files often accumulate variables, calculations, and CSS rules in the same module. A spacing scale might be defined as a map, then consumed by a mixin that also writes margin and padding declarations. When the same file handles both computation and output, it becomes hard to reuse the values in JavaScript, hard to audit the generated CSS, and easy to accidentally duplicate rules.
Useful takeaway
Separate logic from output. Use Sass functions to calculate and return a single value, and use mixins only to emit CSS declarations. Load both through the @use module system so variables stay namespaced and do not leak into the global scope. This keeps the design system predictable and makes the compiled CSS easier to inspect.
Requirements
- A central token map for values such as spacing, color, or breakpoints.
- A way to look up a token by name and fail clearly when the name is missing.
- A reusable pattern that emits CSS without repeating declarations.
- Module boundaries so token names do not collide across packages.
Smallest suitable design
One Sass module holds the map, one function performs the lookup, and one mixin consumes the lookup to emit layout CSS. The function never writes CSS; the mixin never performs business logic beyond calling the function.
Example module: _tokens.scss
$space: (
xs: 0.25rem,
sm: 0.5rem,
md: 1rem,
lg: 1.5rem,
);
@function space($key) {
@if not map.has-key($space, $key) {
@error "Unknown spacing token: #{$key}";
}
@return map.get($space, $key);
}
@mixin stack($gap-key: md) {
$gap: space($gap-key);
display: flex;
flex-direction: column;
gap: $gap;
> * + * {
margin-top: $gap;
}
}
Terms: a function returns a value and can be used on the right side of a CSS property. A mixin emits CSS declarations into the selector that includes it. @use loads a module under a namespace, so the map $space is not available globally.
Consuming the module
@use "tokens";
.card {
@include tokens.stack(sm);
}
Compiled output should resemble:
.card {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.card > * + * {
margin-top: 0.5rem;
}
That output block is illustrative of the expected structure, not a record of a compiled file.
Trust and data boundaries
The token map is trusted input maintained by the design system team. Arbitrary values passed to space() are untrusted; the @error guard stops compilation before invalid tokens reach the CSS. Because @use namespaces the module, tokens.stack cannot accidentally override a stack mixin from another library.
Operational checks
- Install Dart Sass. The legacy LibSass implementation does not support @use.
- Run from the project root: dart sass src/styles.scss public/styles.css. This requires read access to src/ and write access to public/.
- Inspect the output. Confirm that gap and margin-top contain the calculated token value, not the variable name.
- Confirm namespace isolation. Referencing $space outside the tokens module should produce a Sass error.
- Audit for duplicates. Search the compiled CSS for repeated .card blocks; repeated inclusions of the same mixin in nested selectors can duplicate declarations.
Risk: the compile command overwrites public/styles.css. Write to a staging path or use a build tool with source maps if you need to preserve the previous output.
Failure modes
- Bloat from nested mixins. Including stack inside multiple nested selectors repeats display: flex and gap in every rule, increasing file size and specificity.
- Wrong tool for the job. Calling tokens.space() where a CSS declaration is expected produces no output; calling a mixin where a value is expected produces a Sass error.
- Build-time cost. Complex map logic inside functions runs on every compilation and can slow large builds.
- Compiler mismatch. @use fails under LibSass or older Sass implementations.
Conditions that would change the design
- If tokens must be read by JavaScript at runtime, replace the Sass map with CSS custom properties and expose them through a style tag or JSON build step.
- If the build pipeline is locked to LibSass, avoid @use and prefix mixin and function names manually instead of relying on namespaces.
- If theming requires runtime class switching, prefer CSS custom properties over compile-time Sass maps.
Practical verification
After compiling, run grep -n "margin-top" public/styles.css and confirm the value matches the token map. Then change one token value in _tokens.scss, recompile, and confirm the value changed in the output without editing any selector. If the value does not change, the module is likely cached or the wrong file is being compiled.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.