Skip to main content

CSS Custom Properties (Variables)

CSS custom properties, commonly called "CSS variables," are one of the most powerful modern CSS features. They enable dynamic theming, maintainable design systems, and real-time JavaScript integration — things preprocessor variables simply cannot do.

1. Declaration and Syntax

CSS variables use the double-hyphen prefix -- and are declared inside a selector block:

:root {
/* Naming convention: --category-name-modifier */
--color-primary: #6c63ff;
--font-size-base: 1rem;
--space-md: 1.5rem;
}

:root is the recommended place for global variables — it targets the <html> element with the highest specificity of any element selector.

2. The var() Function

/* Syntax: var(--variable-name, fallback-value) */
.button {
background: var(--color-primary);
padding: var(--space-md);
font-size: var(--font-size-base, 1rem); /* Fallback if variable is undefined */
}

Nested Fallbacks

.text {
/* Try --text-color, then --color-text, then hardcoded #111 */
color: var(--text-color, var(--color-text, #111));
}

3. Scope: Local vs Global

Variables cascade like all CSS — you can scope them to a component:

/* Global */
:root { --card-bg: #fff; }

/* Scoped override — only affects .dark-section and its children */
.dark-section {
--card-bg: #1c1c1e;
--color-text: #f0f0f0;
}

.card { background: var(--card-bg); } /* Automatically adjusts in dark sections */

4. Complete Design Token System

A production-ready token set to paste into your :root:

:root {
/* ── Color Palette ── */
--hue: 245;
--color-primary: hsl(var(--hue), 80%, 62%);
--color-primary-dark: hsl(var(--hue), 80%, 48%);
--color-secondary: hsl(35, 90%, 58%);
--color-bg: #0f0f0f;
--color-surface: #1a1a1a;
--color-surface-2: #242424;
--color-border: rgba(255, 255, 255, 0.08);
--color-text: #f0f0f0;
--color-text-muted: #888888;
--color-danger: hsl(0, 75%, 55%);
--color-success: hsl(145, 65%, 45%);
--color-warning: hsl(40, 95%, 55%);

/* ── Typography ── */
--font-heading: 'Playfair Display', Georgia, serif;
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'Fira Code', 'Courier New', monospace;

--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.25rem;
--text-xl: 1.5rem;
--text-2xl: 2rem;
--text-3xl: clamp(2rem, 4vw, 3rem);
--text-hero: clamp(2.5rem, 6vw, 5rem);

/* ── Spacing ── */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-24: 6rem; /* 96px */

/* ── Border Radius ── */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-xl: 24px;
--radius-full: 9999px;

/* ── Shadows ── */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.15);
--shadow-md: 0 4px 12px rgba(0,0,0,0.3);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.5);

/* ── Transitions ── */
--ease-fast: 0.15s ease;
--ease-normal: 0.3s ease;
--ease-slow: 0.6s ease;
--ease-bounce: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

5. Dark Mode with Variables

:root { --bg: #fff; --text: #111; --card: #f5f5f5; }

@media (prefers-color-scheme: dark) {
:root { --bg: #0f0f0f; --text: #f0f0f0; --card: #1c1c1e; }
}

/* Or via attribute/class for a JS toggle */
[data-theme="dark"] {
--bg: #0f0f0f;
--text: #f0f0f0;
--card: #1c1c1e;
}

6. JavaScript Integration

// Read a variable
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary').trim();

// Write a variable
document.documentElement.style.setProperty('--color-primary', '#00ff87');

// Dark mode toggle
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
document.documentElement.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
localStorage.setItem('theme', current === 'dark' ? 'light' : 'dark');
}

// Restore on page load
document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'light');

7. Variables vs Sass Variables

FeatureCSS VariablesSass Variables
Computed atRuntime (in browser)Compile time
Can be changed by JS✅ Yes❌ No
Can be scoped✅ Yes (any selector)❌ Global only
Dark mode toggle✅ Easy❌ Requires duplication
Browser support✅ 97%+Needs build step
Fallback valuesvar(--x, fallback)❌ None

[!TIP] In modern WordPress projects, use CSS Custom Properties for everything — theme colors, spacing, font sizes. They're natively supported by GeneratePress and Bricks Builder, and you can update them at runtime without rebuilding.