Skip to main content

CSS Variables & Mixins - Explained

Understanding CSS custom properties (variables) and preprocessor mixins for maintainable, scalable stylesheets

Overview

Category:UI FUNDAMENTALS
Difficulty:INTERMEDIATE
Last Updated:2025-09-21
Tags:
cssvariablescustom-propertiesmixinssasspreprocessorstheming

Understanding CSS custom properties (variables) and preprocessor mixins for maintainable, scalable stylesheets

Reference Content

Understanding Maintainable CSS

The Problem: Hard-coded values make CSS maintenance nightmare. Change a brand color? Search and replace 47 instances. Add dark mode? Duplicate half your stylesheet. The Solution: Variables store values once, use everywhere. Mixins package reusable patterns. Both create maintainable, scalable CSS. Key Difference:

  • CSS Variables: Runtime values that can change dynamically (theming, JavaScript)
  • Sass Variables: Compile-time values that get replaced during build
  • Mixins: Reusable blocks of CSS declarations

CSS Custom Properties (Variables)

What it does: Stores values that can be reused throughout your stylesheet and changed dynamically. Why use it: Single source of truth for design tokens. Change once, update everywhere. Enable dynamic theming. How it works:
1. Declare variables with -- prefix (anywhere, but :root for globals)
2. Use with var() function anywhere you'd use a value
3. Browser resolves variables at render time, not compile time

/ Declaration /
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--spacing-unit: 8px;
--border-radius: 4px;
--font-main: 'Helvetica', sans-serif;
}

/ Usage /
.element {
color: var(--primary-color);
padding: var(--spacing-unit);
border-radius: var(--border-radius);
font-family: var(--font-main);
}

Theme System with CSS Variables

/ Default (light) theme /
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #333333;
--text-secondary: #666666;
--border-color: #dddddd;
--shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/ Dark theme /
[data-theme="dark"] {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #ffffff;
--text-secondary: #b0b0b0;
--border-color: #404040;
--shadow: 0 2px 4px rgba(0,0,0,0.3);
}

/ Usage remains the same /
.card {
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--border-color);
box-shadow: var(--shadow);
}

Sass/SCSS Mixins

// Define mixin
@mixin button-style {
padding: 10px 20px;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.3s ease;
}

// Use mixin
.button {
@include button-style;
background: blue;
color: white;
}

CSS Variables vs Sass Variables

When to Use Each:

  • Sass Variables: Build-time constants (breakpoints, font stacks, asset paths)
  • CSS Variables: Runtime values (colors, spacing, user preferences)
  • Best Practice: Use both together for maximum power
Comparison Table:
FeatureSass VariablesCSS Variables
TimingCompile-timeRuntime
JavaScript AccessNoYes
ScopeGlobal onlyInherit/Override
AnimationNoYes
FunctionsAll Sass functionscalc() only
Browser Support100% (compiles away)95%+ (IE11 needs polyfill)
PerformanceFaster (pre-compiled)Slightly slower
// Sass variables (compile-time)
$primary-color: #3498db;
$breakpoint-tablet: 768px;
$font-stack: 'Helvetica', Arial, sans-serif;

.element {
// Resolved during Sass compilation
color: $primary-color;
font-family: $font-stack;

// Can use Sass functions
background: lighten($primary-color, 20%); // #5dade2
border: 1px solid darken($primary-color, 15%); // #2874a6

@media (min-width: $breakpoint-tablet) {
font-size: 1.2rem;
}
}

/ Output CSS (variables are gone): /
.element {
color: #3498db;
font-family: 'Helvetica', Arial, sans-serif;
background: #5dade2;
border: 1px solid #2874a6;
}

@media (min-width: 768px) {
.element { font-size: 1.2rem; }
}

/ CSS variables (runtime) /
:root {
--primary-color: #3498db;
--primary-light: #5dade2;
--opacity-level: 0.8;
}

.element {
/ Resolved by browser at runtime /
color: var(--primary-color);
background: var(--primary-light);

/ Can use calc() for math /
opacity: calc(var(--opacity-level) * 0.9);

/ JavaScript can change these live /
transition: color 0.3s ease;
}

/ JavaScript can do: /
/ document.documentElement.style.setProperty('--primary-color', '#e74c3c'); /

Combined Power:
// Define constants with Sass
$brand-blue: #3498db;
$brand-green: #2ecc71;
$spacing-base: 16px;

// Inject into CSS variables for runtime flexibility
:root {
--color-primary: #{$brand-blue}; // Convert Sass var to CSS var
--color-secondary: #{$brand-green};
--spacing-unit: #{$spacing-base};
--spacing-small: #{$spacing-base * 0.5};
--spacing-large: #{$spacing-base * 2};
}

// Use both as needed
.component {
// Sass functions for color manipulation
border: 1px solid #{lighten($brand-blue, 10%)};

// CSS variables for theming
background: var(--color-primary);
padding: var(--spacing-unit);

@media (min-width: $breakpoint-tablet) {
padding: var(--spacing-large);
}
}

Real-world strategy: Major sites like GitHub use Sass for build-time constants and CSS variables for user-customizable values.

Advanced Patterns

/ Component with customizable properties /
.button {
/ Define defaults /
--button-bg: var(--primary, #3498db);
--button-color: var(--on-primary, white);
--button-padding: var(--space-md, 1rem);
--button-radius: var(--radius-md, 4px);

/ Apply properties /
background: var(--button-bg);
color: var(--button-color);
padding: var(--button-padding);
border-radius: var(--button-radius);
}

/ Variations /
.button--large {
--button-padding: var(--space-lg);
}

.button--rounded {
--button-radius: 999px;
}

.button--ghost {
--button-bg: transparent;
--button-color: var(--primary);
border: 2px solid currentColor;
}

JavaScript Integration

What it does: Allows dynamic theming, user preferences, and interactive color schemes. Why use it: Create theme switchers, user customization, and responsive design systems. How it works: Variables are CSS properties, so you can read/write them like any style property.

// Get current variable value
const root = document.documentElement;
const primaryColor = getComputedStyle(root)
.getPropertyValue('--primary-color')
.trim(); // Remove whitespace

console.log(primaryColor); // "#3498db"

// Set global variable (affects entire page)
root.style.setProperty('--primary-color', '#e74c3c');
root.style.setProperty('--font-scale', '1.2');

// Set variable on specific element (affects element and children)
const card = document.querySelector('.card');
card.style.setProperty('--card-bg', '#f0f0f0');
card.style.setProperty('--card-padding', '2rem');

// Remove variable (falls back to parent or default)
root.style.removeProperty('--primary-color');

// Check if variable is supported
if (CSS.supports('color', 'var(--test-var)')) {
// Browser supports CSS variables
enableAdvancedTheming();
} else {
// Fallback for older browsers
useStaticColors();
}

// Animate variables with JavaScript
function animateHue() {
let hue = 0;
const element = document.querySelector('.dynamic-bg');

setInterval(() => {
hue = (hue + 1) % 360;
element.style.setProperty('--hue', hue);
}, 50); // Smooth color wheel animation
}

Real-world examples:
// Theme switcher
function setTheme(themeName) {
document.documentElement.setAttribute('data-theme', themeName);
localStorage.setItem('theme', themeName);

// Update individual variables if needed
if (themeName === 'dark') {
document.documentElement.style.setProperty('--shadow-opacity', '0.3');
} else {
document.documentElement.style.setProperty('--shadow-opacity', '0.1');
}
}

// User color picker
colorPicker.addEventListener('change', (e) => {
const color = e.target.value;
document.documentElement.style.setProperty('--accent-color', color);

// Generate complementary colors
const hsl = hexToHsl(color);
const complementary = hsl(${(hsl.h + 180) % 360}, ${hsl.s}%, ${hsl.l}%);
document.documentElement.style.setProperty('--accent-complement', complementary);
});

// Responsive variables based on viewport
function updateViewportVariables() {
const vw = window.innerWidth;
const vh = window.innerHeight;

// Dynamic spacing based on screen size
const baseSpacing = Math.max(8, vw * 0.01); // 1% of width, min 8px
document.documentElement.style.setProperty('--dynamic-spacing', baseSpacing + 'px');

// Dynamic font scaling
const fontSize = Math.max(14, Math.min(20, vw * 0.02));
document.documentElement.style.setProperty('--dynamic-font-size', fontSize + 'px');
}

window.addEventListener('resize', updateViewportVariables);
updateViewportVariables(); // Set initial values

Common pitfalls:
  • CSS variables are strings, not numbers: getPropertyValue() returns "16px", not 16
  • Whitespace matters: always .trim() returned values
  • Variable names are case-sensitive: --Color--color
Performance tip: Batch variable updates to prevent multiple repaints:
// ❌ Bad - causes multiple repaints
element.style.setProperty('--color1', 'red');
element.style.setProperty('--color2', 'blue');
element.style.setProperty('--color3', 'green');

// ✅ Good - single repaint
element.style.cssText = `
--color1: red;
--color2: blue;
--color3: green;
`;