CSS Variables (Custom Properties)

Difficulty: Intermediate

Question

How do CSS custom properties work? How are they different from Sass variables?

Answer

CSS custom properties (variables) are declared with -- prefix and used with var() function.

Key differences from Sass variables: 1. CSS variables cascade and inherit through the DOM 2. CSS variables can be changed at runtime with JavaScript 3. CSS variables can be scoped to any selector (not just global) 4. CSS variables work with media queries dynamically 5. Sass variables are compiled away - they don't exist at runtime

CSS variables enable: - Theming (dark mode, brand colors) - Component-scoped values - Runtime style changes without JavaScript - Responsive design without duplicating properties

Code examples

Declaration and Usage

/* Declare on :root for global scope */
:root {
  --color-primary: #3b82f6;
  --color-text: #1f2937;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --radius: 0.5rem;
  --font-sans: 'Inter', system-ui, sans-serif;
}

/* Use with var() */
.button {
  background: var(--color-primary);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius);
  font-family: var(--font-sans);
}

/* Fallback value */
.card {
  background: var(--card-bg, white);
  /* Uses white if --card-bg is not defined */
}

/* Computed values */
.element {
  --size: 4;
  width: calc(var(--size) * 1rem); /* 4rem */
}

Variables declared on :root are available everywhere. var() accepts a fallback value as second argument. Use calc() for computed values.

Theming with CSS Variables

/* Light theme (default) */
:root {
  --bg: #ffffff;
  --bg-surface: #f9fafb;
  --text: #111827;
  --text-muted: #6b7280;
  --border: #e5e7eb;
  --shadow: 0 1px 3px rgb(0 0 0 / 0.1);
}

/* Dark theme */
.dark {
  --bg: #111827;
  --bg-surface: #1f2937;
  --text: #f9fafb;
  --text-muted: #9ca3af;
  --border: #374151;
  --shadow: 0 1px 3px rgb(0 0 0 / 0.4);
}

/* Components use variables */
body {
  background: var(--bg);
  color: var(--text);
}
.card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}

/* Toggle with JS */
/* document.documentElement.classList.toggle('dark') */

Toggle the dark class on the html element, and every component updates instantly. No need to change individual element styles.

Component-Scoped Variables

/* Variables scoped to a component */
.btn {
  --btn-bg: #3b82f6;
  --btn-color: white;
  --btn-size: 0.875rem;

  background: var(--btn-bg);
  color: var(--btn-color);
  font-size: var(--btn-size);
  padding: 0.5em 1em;
}

/* Variants override scoped variables */
.btn--danger {
  --btn-bg: #ef4444;
}
.btn--outline {
  --btn-bg: transparent;
  --btn-color: #3b82f6;
}
.btn--lg {
  --btn-size: 1.125rem;
}

/* Change variables via JavaScript */
/* element.style.setProperty('--btn-bg', '#10b981') */

/* Responsive variables */
:root {
  --container-padding: 1rem;
}
@media (min-width: 768px) {
  :root {
    --container-padding: 2rem;
  }
}

Scoped variables make component variants clean - just override the variable instead of repeating all the properties.

Key points

Concepts covered

Custom Properties, CSS Variables, Theming, Cascading, Dynamic Styles