🔥 Your ₹599 Course Awaits — Grab It Before It’s Gone!

Join thousands of learners building their careers with Prime Inspire. Your exclusive ₹599 coupon is just one click away.

Subscription Form

How to Use CSS Variables in SCSS (Step-by-Step)

How to Use CSS Variables in SCSS (Step-by-Step)

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 (sass package)
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. Use color-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

Discover more from Prime Inspire

Subscribe to get the latest posts sent to your email.

We’d love to hear your thoughts! Share your ideas below 💡

Scroll to Top

Discover more from Prime Inspire

Subscribe now to keep reading and get access to the full archive.

Continue reading