TLDR
Use CSS variables for design tokens in 2026. CSS custom properties offer runtime theming, scoped overrides, and better maintainability compared to Sass variables, which are compile-time only. The only remaining reason to use Sass variables is for complex computed values or legacy projects that can't adopt CSS variables due to older build constraints.
Key takeaways:
- CSS variables enable runtime theming (dark mode, user preferences) that Sass cannot achieve
- CSS variables can be scoped to components and overridden dynamically; Sass variables are global or file-scoped only
- Sass variables have zero runtime cost but lock in values at compile time
- Hybrid approach (CSS variables for tokens, Sass for computed logic) works well during migration
- All major design token tools (Style Dictionary, Theo) output CSS variables as the default format
- CSS custom properties are the W3C standard for design tokens going forward
Key Differences at a Glance
| Feature | CSS Variables (--token-name) |
Sass Variables ($token-name) |
|---|---|---|
| Resolution | Runtime (browser) | Compile-time (build) |
| Scoping | Can be scoped to any selector, inherited | Global or file-scoped only |
| Theming | Yes (change values dynamically via JS or media queries) | No (locked at build time) |
| Browser Support | All modern browsers (98%+ since 2017) | N/A (compiled away) |
| Performance | Slight runtime overhead for resolution | Zero runtime cost |
| Debugging | Visible in DevTools as --var-name |
Not visible (compiled to static values) |
| Fallbacks | Built-in: var(--primary, #blue) |
Requires manual conditional logic |
| Overrides | Can override via specificity or inline styles | Cannot override after compilation |
| Computed Values | Limited (calc() only) | Full Sass functions available |
| Cross-file References | Must be in scope (usually :root) |
Import anywhere in Sass |
Bottom line: CSS variables win for everything except complex compile-time computations.
CSS Custom Properties for Design Tokens
CSS custom properties (CSS variables) are native browser features. You define them with -- prefix and reference them with var() function:
:root {
--color-primary: #3b82f6;
--spacing-base: 16px;
}
.button {
background-color: var(--color-primary);
padding: var(--spacing-base);
}Runtime Resolution
This is the key difference. CSS variables are resolved at runtime by the browser. That means you can:
1. Change values with JavaScript:
document.documentElement.style.setProperty('--color-primary', '#ff0000');
// All elements using var(--color-primary) update instantly2. Override values in media queries:
:root {
--font-size-base: 16px;
}
@media (max-width: 768px) {
:root {
--font-size-base: 14px;
}
}3. Create dark mode themes:
:root {
--bg-primary: #ffffff;
--text-primary: #000000;
}
:root[data-theme="dark"] {
--bg-primary: #1a1a1a;
--text-primary: #ffffff;
}Toggle the theme with one line of JavaScript:
document.documentElement.setAttribute('data-theme', 'dark');None of this is possible with Sass variables. Sass variables are compiled to static values at build time and cannot be changed after that.
Scoped Overrides
CSS variables respect the cascade. You can define a global value and override it for specific components:
:root {
--button-padding: 16px;
}
.button-small {
--button-padding: 8px; /* Override for this component */
}
.button {
padding: var(--button-padding); /* Uses scoped value */
}This pattern is extremely powerful for component libraries. Each component can override design tokens locally without affecting global values.
Debugging in DevTools
CSS variables appear in browser DevTools as actual properties:
.button {
background-color: var(--color-primary);
--color-primary: #3b82f6; /* visible in DevTools */
}
You can edit the variable value in DevTools and see changes instantly. With Sass, you see the compiled output (background-color: #3b82f6) with no reference to the original variable.
Fallback Values
CSS variables support built-in fallbacks:
color: var(--color-primary, #3b82f6);If --color-primary is undefined, the browser uses #3b82f6. You can chain fallbacks:
color: var(--color-brand-primary, var(--color-primary, #3b82f6));This provides progressive enhancement: if a token isn't defined, fall back to a sensible default.
Sass Variables for Design Tokens
Sass variables use $ prefix and are resolved at compile time:
$color-primary: #3b82f6;
$spacing-base: 16px;
.button {
background-color: $color-primary;
padding: $spacing-base;
}Compiled output:
.button {
background-color: #3b82f6;
padding: 16px;
}The variable references are gone. The browser sees static values.
Compile-Time Optimization
Because Sass variables are resolved during build, they have zero runtime cost. There's no variable lookup when the page loads—just static CSS values.
For extremely performance-sensitive applications (high-frequency trading dashboards, browser games), this matters. For 99.9% of web applications, the difference is imperceptible.
Computed Values with Sass Functions
Sass supports powerful functions and computations:
$base-spacing: 16px;
// Computed scales using Sass functions
$spacing-xs: $base-spacing * 0.25; // 4px
$spacing-sm: $base-spacing * 0.5; // 8px
$spacing-md: $base-spacing; // 16px
$spacing-lg: $base-spacing * 1.5; // 24px
$spacing-xl: $base-spacing * 2; // 32px
// Color manipulation
$primary: #3b82f6;
$primary-dark: darken($primary, 10%);
$primary-light: lighten($primary, 20%);
// Conditional logic
$enable-rounded-corners: true;
$border-radius: if($enable-rounded-corners, 4px, 0);CSS custom properties can only use calc() for math. They have no equivalent to Sass's darken(), lighten(), if(), or other functions.
However: Most design systems don't need computed values. You define tokens explicitly (not via computation), so this Sass advantage rarely matters in practice.
File-Scoped Organization
Sass variables can be organized across files:
_colors.scss:
$color-primary: #3b82f6;
$color-secondary: #8b5cf6;_spacing.scss:
$spacing-base: 16px;
$spacing-large: 24px;main.scss:
@import 'colors';
@import 'spacing';
.button {
background: $color-primary;
padding: $spacing-base;
}CSS variables require all tokens to be in scope (usually defined in :root) or explicitly imported. Sass's @import is more flexible for organizing large token sets.
Counterpoint: With CSS @import or build tools that bundle CSS, you can achieve similar organization with CSS variables.
Code Example: Same Token Both Ways
Let's implement an identical design token system using CSS variables and Sass variables to see the practical differences.
Design Tokens (both approaches)
We'll implement:
- Color palette (primary, secondary, neutral scale)
- Spacing scale
- Typography tokens
CSS Variables Implementation
tokens.css:
:root {
/* Color palette */
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-purple-500: #8b5cf6;
--color-gray-50: #f9fafb;
--color-gray-900: #111827;
/* Semantic colors */
--color-primary: var(--color-blue-500);
--color-primary-hover: var(--color-blue-600);
--color-secondary: var(--color-purple-500);
--color-bg-primary: var(--color-gray-50);
--color-text-primary: var(--color-gray-900);
/* Spacing scale */
--spacing-1: 4px;
--spacing-2: 8px;
--spacing-3: 12px;
--spacing-4: 16px;
--spacing-6: 24px;
--spacing-8: 32px;
/* Typography */
--font-family-base: Inter, sans-serif;
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
--font-weight-normal: 400;
--font-weight-semibold: 600;
}
/* Dark mode overrides */
:root[data-theme="dark"] {
--color-bg-primary: #1a1a1a;
--color-text-primary: #f9fafb;
}Usage:
.button {
background-color: var(--color-primary);
color: white;
padding: var(--spacing-3) var(--spacing-6);
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
}
.button:hover {
background-color: var(--color-primary-hover);
}Sass Variables Implementation
_tokens.scss:
// Color palette
$color-blue-500: #3b82f6;
$color-blue-600: #2563eb;
$color-purple-500: #8b5cf6;
$color-gray-50: #f9fafb;
$color-gray-900: #111827;
// Semantic colors
$color-primary: $color-blue-500;
$color-primary-hover: $color-blue-600;
$color-secondary: $color-purple-500;
$color-bg-primary: $color-gray-50;
$color-text-primary: $color-gray-900;
// Spacing scale
$spacing-1: 4px;
$spacing-2: 8px;
$spacing-3: 12px;
$spacing-4: 16px;
$spacing-6: 24px;
$spacing-8: 32px;
// Typography
$font-family-base: Inter, sans-serif;
$font-size-sm: 14px;
$font-size-base: 16px;
$font-size-lg: 18px;
$font-weight-normal: 400;
$font-weight-semibold: 600;Usage:
@import 'tokens';
.button {
background-color: $color-primary;
color: white;
padding: $spacing-3 $spacing-6;
font-family: $font-family-base;
font-size: $font-size-base;
font-weight: $font-weight-semibold;
&:hover {
background-color: $color-primary-hover;
}
}Compiled output:
.button {
background-color: #3b82f6;
color: white;
padding: 12px 24px;
font-family: Inter, sans-serif;
font-size: 16px;
font-weight: 600;
}
.button:hover {
background-color: #2563eb;
}Key Differences in Practice
CSS variables:
- ✅ Can toggle dark mode by changing one attribute (
data-theme="dark") - ✅ Token references visible in DevTools (
var(--color-primary)) - ✅ Can override tokens with inline styles:
style="--color-primary: red" - ❌ Slightly more verbose syntax (
var(--token)vs$token)
Sass variables:
- ✅ Cleaner syntax (
$tokenvsvar(--token)) - ✅ Zero runtime overhead (static values)
- ❌ No dark mode (would require separate stylesheets or duplicated selectors)
- ❌ No runtime changes (locked at compile time)
- ❌ Not visible in DevTools (compiled away)
When CSS Variables Win
CSS variables are the better choice for:
1. Theming (Dark Mode, User Preferences)
This is the killer feature. Implementing dark mode with Sass requires duplicating all your styles:
Sass approach (verbose):
.button {
background: $bg-primary-light;
color: $text-primary-light;
}
[data-theme="dark"] .button {
background: $bg-primary-dark;
color: $text-primary-dark;
}CSS variables approach (concise):
:root { --bg-primary: white; --text-primary: black; }
:root[data-theme="dark"] { --bg-primary: #1a1a1a; --text-primary: white; }
.button {
background: var(--bg-primary);
color: var(--text-primary);
}Change one attribute, entire theme updates. No duplicated selectors.
2. Runtime Overrides
Need to let users customize brand colors via settings panel?
function updateBrandColor(color) {
document.documentElement.style.setProperty('--color-primary', color);
}Every component using var(--color-primary) updates instantly. Impossible with Sass.
3. Component-Scoped Tokens
Want a specific component to use different spacing without affecting global tokens?
.compact-layout {
--spacing-base: 8px; /* Override just for this section */
}All child components using var(--spacing-base) inherit the scoped value. Clean and maintainable.
4. Progressive Enhancement
CSS variables support fallbacks for browsers that don't support a token (rare, but possible):
background: var(--color-primary, #3b82f6);If --color-primary is undefined, use #3b82f6. Graceful degradation built-in.
5. Design Token Tooling
Modern design token tools like Style Dictionary and Theo output CSS variables by default. They treat CSS custom properties as the standard format for web tokens.
Using Sass variables means converting the output, adding an extra build step.
When Sass Still Makes Sense
There are a few scenarios where Sass variables remain useful:
1. Complex Computed Values
If your design system relies heavily on computed scales:
$base-size: 16px;
$scale-ratio: 1.25;
$size-1: $base-size;
$size-2: $base-size * $scale-ratio;
$size-3: $base-size * pow($scale-ratio, 2);
$size-4: $base-size * pow($scale-ratio, 3);CSS can't do this with calc() alone. However, most teams define explicit values rather than computed scales, so this advantage is narrow.
2. Legacy Projects
If you're maintaining a large Sass codebase and can't justify a migration, Sass variables work fine. There's no urgent need to rewrite working code.
3. Build-Time Optimization (Edge Cases)
For applications with extreme performance requirements (high-frequency dashboards, games), eliminating the CSS variable runtime lookup might matter. Test before optimizing—the difference is usually negligible.
Migration Strategy: Sass to CSS Variables
If you're migrating from Sass variables to CSS variables, use this incremental approach:
Step 1: Hybrid Phase – Reference CSS Variables from Sass
Define CSS variables:
:root {
--color-primary: #3b82f6;
--spacing-base: 16px;
}Reference them in Sass:
.button {
background: var(--color-primary); // Use CSS variable
padding: var(--spacing-base);
}This lets you gradually convert tokens to CSS variables while keeping Sass for other features (mixins, nesting).
Step 2: Convert Global Tokens First
Start with global design tokens (colors, spacing, typography). These benefit most from CSS variables because they're reused everywhere.
Keep component-specific Sass variables temporarily:
// Global tokens converted to CSS variables (in CSS file)
:root { --color-primary: #3b82f6; }
// Component-specific Sass variable (not worth converting yet)
$button-shadow: 0 2px 4px rgba(0,0,0,0.1);
.button {
background: var(--color-primary); // CSS variable
box-shadow: $button-shadow; // Sass variable
}Step 3: Test Theming Features
Implement dark mode or user theming to validate CSS variables work correctly:
:root { --color-bg: white; }
:root[data-theme="dark"] { --color-bg: #1a1a1a; }If theming works, you're benefiting from CSS variables. Continue migrating.
Step 4: Migrate Component-Specific Tokens
Finally, convert component-specific tokens if needed. Many teams stop at Step 2 (global tokens as CSS variables, component logic in Sass) and that's fine.
Step 5: Remove Sass Dependency (Optional)
If you've eliminated all Sass variables and only use CSS variables, consider removing Sass entirely. You can keep Sass for other features (mixins, functions) even if all tokens are CSS variables.
For more details on design token implementation with CSS variables, see our guide on CSS variables for design tokens.
Modern Design Systems Use CSS Variables
Survey the largest design systems:
- Material Design (Google): CSS variables for theming
- Fluent (Microsoft): CSS variables for all tokens
- Carbon (IBM): CSS variables with Sass fallback
- Polaris (Shopify): CSS variables for theming
- Atlassian Design System: CSS variables for color tokens
The industry has converged on CSS variables for design tokens. Sass is still used for logic and mixins, but not for token storage.
Why? Theming is now a baseline requirement. Users expect dark mode, high-contrast mode, and customizable interfaces. CSS variables make this trivial; Sass makes it painful.
Hybrid Approach: Best of Both
You don't have to choose exclusively. Many teams use:
- CSS variables for design tokens (colors, spacing, typography)
- Sass for logic (mixins, functions, loops, conditionals)
Example:
// Design tokens defined as CSS variables (in separate CSS file)
:root {
--color-primary: #3b82f6;
--spacing-base: 16px;
}
// Sass mixin using CSS variables
@mixin button-variant($bg-var, $text-var) {
background: var($bg-var);
color: var($text-var);
padding: var(--spacing-base);
&:hover {
background: color-mix(in srgb, var($bg-var) 90%, black);
}
}
.button-primary {
@include button-variant(--color-primary, --color-white);
}This combines CSS variable runtime theming with Sass's powerful logic and reusability.
Tailwind and CSS Variables
If you're using Tailwind CSS for your design system, you're likely already using CSS variables. Tailwind v3+ uses CSS variables for theming:
@layer base {
:root {
--color-primary: 59 130 246; /* RGB values */
}
}
@layer utilities {
.bg-primary {
background-color: rgb(var(--color-primary));
}
}Tailwind's opacity modifiers rely on CSS variables to work dynamically:
<div class="bg-primary/50">
<!-- 50% opacity via CSS variable -->
</div>This wouldn't be possible with Sass variables. See our Tailwind design tokens guide for full integration patterns.
FAQ
Q: Are CSS variables faster than Sass variables?
A: CSS variables have a slight runtime overhead because browsers resolve them at runtime, while Sass variables are resolved at compile time and become static values. However, this performance difference is negligible in practice. The real advantage of CSS variables is runtime theming and dynamic updates, which Sass cannot do.
Q: Can I migrate from Sass variables to CSS variables gradually?
A: Yes. Start by converting global tokens to CSS variables while keeping component-specific Sass variables. Use Sass to reference CSS variables with var() function calls. This hybrid approach lets you adopt CSS variables incrementally without a complete rewrite, testing theming features while maintaining existing code.
Q: Do CSS variables work in all browsers in 2026?
A: Yes. CSS custom properties have been supported in all major browsers since 2017, with global usage above 98% as of 2026. Internet Explorer 11 was the last major browser without support, and it reached end-of-life in 2022. CSS variables are now safe for production use without polyfills.