
CSS variables (custom properties) work at runtime and cascade; SCSS variables compile away at build time. Use them together to get themeable, dynamic UIs with solid fallbacks.
Why mix CSS variables with SCSS?
- SCSS variables (
$primary: #0ea5e9;) are replaced at compile time—great for design-time math and generating lots of CSS. - CSS variables (
--primary: #0ea5e9;) live in the browser—great for theming, dark mode, and changing values with JS or per-component scopes.
Combining them gives you the best of both worlds: dynamic theming + ergonomic authoring.
New to modern CSS? In my “HTML5 & CSS3” course, you’ll build a solid foundation—selectors, layout, variables, and responsive patterns—so posts like this feel easy and fun.
Prerequisites
- Node 18+ (or any environment that can run a Sass compiler)
- dart-sass (
sasspackage)
npm i -D sass
# Compile once:
npx sass src/styles.scss public/styles.css
# Or watch:
npx sass --watch src/styles.scss:public/styles.css
Code language: PHP (php)
Project structure (suggested)
src/
_tokens.scss
_mixins.scss
styles.scss
public/
index.html
styles.css (built)
Code language: PHP (php)
Step 1 — Define design tokens in SCSS and export as CSS variables
Create src/_tokens.scss:
// SCSS tokens (design-time)
$colors: (
primary: #0ea5e9,
text: #111827,
bg: #ffffff
);
$space: (
1: 0.25rem,
2: 0.5rem,
3: 0.75rem,
4: 1rem
);
// Helper: export nested maps into --custom-properties
@mixin export-custom-props($map, $prefix: null) {
@each $key, $val in $map {
$name: if($prefix, "#{$prefix}-#{$key}", $key);
@if type-of($val) == "map" {
@include export-custom-props($val, $name);
} @else {
--#{$name}: #{$val};
}
}
}
Code language: PHP (php)
Then in src/styles.scss:
@use 'tokens' as *;
// :root holds your default theme
:root {
// Output: --colors-primary, --colors-text, --space-1, etc.
@include export-custom-props($colors, 'colors');
@include export-custom-props($space, 'space');
/* Derived runtime variables (CSS only) */
--radius: 0.625rem;
--brand: var(--colors-primary);
--brand-contrast: #fff;
--gap: var(--space-4);
}
Code language: PHP (php)
What you get: ergonomic SCSS maps for authoring, and runtime CSS variables for theming.
Step 2 — Use CSS variables inside SCSS like a pro
.button {
padding: var(--space-3) var(--space-4);
background: var(--brand);
color: var(--brand-contrast);
border-radius: var(--radius);
}
Code language: CSS (css)
Yes, you can call var(--x) inside SCSS—Sass simply passes it through to the output CSS.
Step 3 — Provide bulletproof fallbacks (SCSS → CSS)
Combine compile-time defaults with runtime overrides:
$primary: map-get($colors, primary);
.card {
// Runtime value first, compile-time fallback second:
background: var(--colors-bg, #fff);
border: 2px solid var(--colors-primary, #{$primary});
color: var(--colors-text, #111827);
}
Code language: PHP (php)
If a CSS variable isn’t set, your SCSS fallback kicks in.
Step 4 — Add dark mode / theming in seconds
// Dark tokens (SCSS map or raw values)
$dark: (
colors: (
primary: #38bdf8,
text: #e5e7eb,
bg: #0b1220
)
);
:root[data-theme="dark"] {
// You can repeat keys you want to override
--colors-primary: map-get(map-get($dark, colors), primary);
--colors-text: map-get(map-get($dark, colors), text);
--colors-bg: map-get(map-get($dark, colors), bg);
--brand-contrast: #0b1220;
}
Code language: PHP (php)
Toggle in HTML or JS:
<html data-theme="dark">Code language: HTML, XML (xml)
or
document.documentElement.toggleAttribute('data-theme', true);Code language: JavaScript (javascript)
Step 5 — Do math at runtime with calc() (and color with color-mix())
You can’t run SCSS functions on var() values (compile vs runtime), but you can use CSS functions that do work at runtime:
:root {
--space-base: 1rem;
--space-lg: calc(var(--space-base) * 1.5);
}
.button--lg {
padding: var(--space-lg);
}
/* Modern color mixing (supported in current evergreen browsers) */
:root {
--brand-600: color-mix(in oklab, var(--brand), black 20%);
}
Code language: CSS (css)
Need guaranteed support for older browsers? Precompute important shades with SCSS and keep the color-mix() version for modern browsers.
Step 6 — Generate a full theme from SCSS maps (DRY)
If you prefer everything in maps, extend the mixin:
$theme: (
colors: (
primary: #0ea5e9,
secondary: #a855f7,
text: #111827,
bg: #ffffff
),
radius: 10px,
spacing: (
sm: .5rem,
md: 1rem,
lg: 1.5rem
)
);
:root { @include export-custom-props($theme); }
.card {
padding: var(--spacing-md);
border-radius: var(--radius);
background: var(--colors-bg);
color: var(--colors-text);
}
Code language: PHP (php)
Step 7 — Change CSS variables live with JavaScript (zero rebuilds)
// Theme on the fly
const root = document.documentElement;
root.style.setProperty('--colors-primary', '#22c55e'); // switch brand to green
root.style.setProperty('--spacing-md', '1.25rem'); // bump spacing
Code language: JavaScript (javascript)
That’s the power CSS variables bring that SCSS alone can’t.
Step 8 — Progressive enhancement & support check
All evergreen browsers support custom properties. If you still care about ancient ones:
@supports not (color: var(--x)) {
/* IE11-era fallback styles (optional, minimal) */
.button { background: #0ea5e9; color: #fff; }
}
Code language: CSS (css)
Common gotchas (and quick fixes)
- You can’t use
var()in selectors or media queries.
This is invalid:@media (min-width: var(--bp)).
Instead, switch variables inside the media query:@media (min-width: 48rem) { :root { --radius: 0.75rem; } } - SCSS functions don’t see runtime values.
lighten(var(--brand), 10%)won’t work. Usecolor-mix()or precompute with SCSS. - Case matters.
--Brand≠--brand. - Scope is real. Set defaults in
:root, then override per component/container when needed.
Real-world snippet you can copy
// styles.scss
$brand: #0ea5e9;
:root {
--brand: #{$brand};
--brand-contrast: #fff;
--gap: 1rem;
}
.button {
display: inline-flex;
align-items: center;
gap: var(--gap);
padding: 0.625rem 1rem;
background: var(--brand, #{$brand});
color: var(--brand-contrast, #fff);
border-radius: 0.5rem;
border: 0;
cursor: pointer;
transition: transform .15s ease;
}
.button:active { transform: translateY(1px); }
:root[data-theme="dark"] {
--brand: #38bdf8;
--brand-contrast: #0b1220;
}
Code language: PHP (php)
FAQ: “How to use CSS variables in SCSS”
Can I assign an SCSS variable to a CSS variable?
Yes—use interpolation:
$primary: #0ea5e9;
:root { --brand: #{$primary}; }
Code language: PHP (php)
Can I compute with SCSS using CSS variables?
No. Do SCSS math on SCSS values, or use CSS functions like calc()/color-mix() for var() values.
How do I theme a single component?
Wrap it:
.card { background: var(--colors-bg); }
.card.theme-warn { --colors-bg: #fff7ed; }
Code language: CSS (css)
Are CSS variables slower?
For typical design tokens, the cost is negligible. Avoid redefining thousands of vars in deep trees.
Quick checklist
- Put defaults in
:root - Use
var(--token, #{$scss-fallback}) - Override with
[data-theme="dark"]or per-component wrappers - Use
calc()/color-mix()for runtime math - Keep tokens in SCSS maps and export to custom properties
Ready to Take Your Web Skills to the Next Level?
If you enjoyed building this feedback form, imagine what you could do with the right guidance and a step-by-step course tailored just for you!
Discover more from Prime Inspire
Subscribe to get the latest posts sent to your email.



