CSS Variables & Mixins - Explained
Understanding CSS custom properties (variables) and preprocessor mixins for maintainable, scalable stylesheets
Overview
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
| Feature | Sass Variables | CSS Variables |
|---|---|---|
| Timing | Compile-time | Runtime |
| JavaScript Access | No | Yes |
| Scope | Global only | Inherit/Override |
| Animation | No | Yes |
| Functions | All Sass functions | calc() only |
| Browser Support | 100% (compiles away) | 95%+ (IE11 needs polyfill) |
| Performance | Faster (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 whitespaceconsole.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
// ❌ 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;
`;