MagmaNex LogoMagmaNex
Home/Blog/CSS Custom Properties (Variables): A Complete Guide
CSS Custom Properties (Variables): A Complete Guide
FrontendMay 23, 2026· 9 min read

CSS Custom Properties (Variables): A Complete Guide

Everything you need to know about CSS custom properties — from basic usage to advanced theming patterns, dark mode, and JavaScript integration.

#css#variables#custom properties#frontend#design system

CSS Custom Properties (Variables): A Complete Guide

CSS Custom Properties (commonly called CSS Variables) are one of the most powerful features in modern CSS. They allow you to define reusable values, build design systems, and create dynamic themes — all without a preprocessor.

What Are CSS Custom Properties?

Unlike Sass or Less variables (which are preprocessed), CSS custom properties are live in the browser. They're part of the CSS cascade and can be changed at runtime with JavaScript.

/* Define a custom property */
:root {
  --color-primary: #6366f1;
  --font-size-base: 16px;
  --spacing-md: 1rem;
}

/* Use it anywhere */
.button {
  background: var(--color-primary);
  font-size: var(--font-size-base);
  padding: var(--spacing-md);
}

Syntax Rules

/* Declaration: always starts with -- */
--my-variable: value;

/* Usage: var() function */
property: var(--my-variable);

/* Fallback value */
property: var(--undefined-var, #ff0000);

/* Nested fallbacks */
property: var(--a, var(--b, var(--c, default)));

Scope and Cascade

Custom properties respect the CSS cascade. Define them globally on :root or scope them to specific elements:

:root {
  --gap: 1rem;      /* Available everywhere */
}

.card {
  --gap: 0.5rem;    /* Overrides inside .card */
  gap: var(--gap);
}

.card__header {
  gap: var(--gap);  /* Gets 0.5rem from parent .card */
}

Building a Design System

The real power of CSS variables is creating a consistent design system:

:root {
  /* Colors */
  --color-primary:    #6366f1;
  --color-primary-dk: #4f46e5;
  --color-secondary:  #ec4899;
  --color-success:    #22c55e;
  --color-warning:    #f59e0b;
  --color-error:      #ef4444;

  /* Typography */
  --font-sans:  'Inter', system-ui, sans-serif;
  --font-mono:  'JetBrains Mono', monospace;
  --text-xs:    0.75rem;
  --text-sm:    0.875rem;
  --text-base:  1rem;
  --text-lg:    1.125rem;
  --text-xl:    1.25rem;
  --text-2xl:   1.5rem;

  /* Spacing (8px base) */
  --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 */

  /* Borders */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 3px rgba(0,0,0,.12);
  --shadow-md: 0 4px 12px rgba(0,0,0,.15);
  --shadow-lg: 0 20px 60px rgba(0,0,0,.25);

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-base: 250ms ease;
}

Dark Mode Theming

This is where CSS variables truly shine. No class toggling across dozens of components:

:root {
  --bg:         #ffffff;
  --bg-2:       #f8fafc;
  --text:       #0f172a;
  --text-muted: #64748b;
  --border:     #e2e8f0;
}

[data-theme="dark"] {
  --bg:         #0d1117;
  --bg-2:       #161b22;
  --text:       #e6edf3;
  --text-muted: #8b949e;
  --border:     #30363d;
}
// Toggle with JavaScript
const toggle = () => {
  const html = document.documentElement
  const current = html.getAttribute('data-theme')
  html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark')
}

Your components never need to change — just the variable values update:

.card {
  background: var(--bg-2);   /* Automatically works in both modes */
  color: var(--text);
  border: 1px solid var(--border);
}

JavaScript Integration

CSS custom properties are fully accessible from JavaScript:

// Read a variable
const root = document.documentElement
const primary = getComputedStyle(root).getPropertyValue('--color-primary').trim()
// → "#6366f1"

// Set a variable (affects all elements using it)
root.style.setProperty('--color-primary', '#10b981')

// Set on a specific element
const card = document.querySelector('.card')
card.style.setProperty('--gap', '2rem')

// Remove an override (falls back to :root value)
card.style.removeProperty('--gap')

Practical Patterns

Component Theming

Let a parent control a component's appearance:

.alert {
  --alert-bg:     #fef3c7;
  --alert-border: #f59e0b;
  --alert-text:   #92400e;

  background: var(--alert-bg);
  border-left: 4px solid var(--alert-border);
  color: var(--alert-text);
}

.alert--error {
  --alert-bg:     #fee2e2;
  --alert-border: #ef4444;
  --alert-text:   #991b1b;
}

.alert--success {
  --alert-bg:     #dcfce7;
  --alert-border: #22c55e;
  --alert-text:   #166534;
}

Responsive Typography

:root {
  --font-size: clamp(14px, 1vw + 12px, 18px);
}

body {
  font-size: var(--font-size);
}

Animation with Variables

@keyframes pulse {
  0%, 100% { transform: scale(var(--scale-start, 1)); }
  50%       { transform: scale(var(--scale-end, 1.05)); }
}

.badge { animation: pulse 2s infinite; }
.badge--alert { --scale-end: 1.15; }

Calculating Derived Values

:root {
  --columns: 3;
  --gap: 1rem;
}

.grid {
  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
  gap: var(--gap);
}

/* Override for mobile */
@media (max-width: 640px) {
  :root { --columns: 1; }
}

CSS Variables vs Preprocessor Variables

Feature CSS Variables Sass/Less Variables
Runtime changeable ✅ Yes ❌ No (compiled out)
JavaScript accessible ✅ Yes ❌ No
Cascade/inheritance ✅ Yes ❌ No
Browser support ✅ All modern ✅ After compilation
Dev tools visibility ✅ Yes ❌ No
Build step required ❌ No ✅ Yes

Browser Support

CSS custom properties are supported in all modern browsers (Chrome 49+, Firefox 31+, Safari 9.1+, Edge 15+). For IE11 support, you'll need a PostCSS plugin — but in 2026, that's rarely a concern.

Tips & Gotchas

  1. Custom properties ARE case-sensitive: --Color--color
  2. They can hold any value — including spaces: --font: 'JetBrains Mono', monospace
  3. They participate in the cascade!important applies normally
  4. Invalid at computed value time doesn't throw an error — it uses the property's initial value
  5. Use @property for typed variables (Chrome 85+):
@property --hue {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

Tools to Help You

Use our Color Converter to convert between HEX, RGB, and HSL when defining your color variables. The CSS Minifier can compress your final stylesheet, and the Gradient Generator helps create beautiful gradient values for your custom properties.

CSS custom properties are a cornerstone of modern CSS architecture. Once you start using them for theming, you'll never go back to hardcoded values.


🛠 Related Tools

📚 Related posts

← All posts🛠 Explore tools