</>StackKit
</>StackKit

Developer tutorials & guides

CSS code with color variables
CSS

CSS Custom Properties (Variables) Guide: Dynamic Theming Done Right

Master CSS custom properties for dynamic theming, dark mode, component variants, and runtime updates — with real-world patterns you can use today.

J
Jordan Kim
March 12, 20257 min read
#css#variables#theming#dark-mode#frontend

What Are CSS Custom Properties?

CSS Custom Properties (often called CSS variables) let you store values in reusable named containers that can be changed dynamically — even at runtime with JavaScript. Unlike preprocessor variables (SASS/LESS), they live in the browser and respond to the cascade.

:root {
  --color-primary: #6366f1;
  --spacing-md: 1rem;
  --radius-lg: 0.75rem;
}

.button {
  background: var(--color-primary);
  padding: var(--spacing-md);
  border-radius: var(--radius-lg);
}

Syntax Deep Dive

/* Define */
--property-name: value;

/* Use */
color: var(--color-text);

/* With fallback */
color: var(--color-text, #333);

/* Fallback can reference another variable */
color: var(--color-heading, var(--color-text, black));

Properties defined on :root are globally available. Properties defined on an element are scoped to it and its children — this is their power.


Building a Theme System

:root {
  /* Colors */
  --color-bg: #ffffff;
  --color-surface: #f8fafc;
  --color-text: #0f172a;
  --color-text-muted: #64748b;
  --color-primary: #6366f1;
  --color-primary-hover: #4f46e5;
  --color-border: #e2e8f0;

  /* Typography */
  --font-sans: 'Inter', system-ui, sans-serif;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-4: 1rem;
  --space-8: 2rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}

Dark Mode in 10 Lines

:root {
  --color-bg: #ffffff;
  --color-text: #0f172a;
  --color-surface: #f8fafc;
  --color-border: #e2e8f0;
}

[data-theme="dark"] {
  --color-bg: #0f172a;
  --color-text: #f1f5f9;
  --color-surface: #1e293b;
  --color-border: #334155;
}
function toggleDarkMode() {
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
  document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
  localStorage.setItem('theme', isDark ? 'light' : 'dark');
}

Respecting System Preference

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0f172a;
    --color-text: #f1f5f9;
  }
}

Combine with the data-theme attribute for a manual override that respects system default.


Component-Scoped Variables

.card {
  --card-padding: 1.5rem;
  --card-radius: 0.75rem;
  --card-shadow: var(--shadow-md);

  padding: var(--card-padding);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
}

.card--compact {
  --card-padding: 0.75rem;  /* override just for compact variant */
}

Updating Variables at Runtime with JavaScript

// Set a variable
document.documentElement.style.setProperty('--color-primary', '#e11d48');

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

This enables user-customizable themes, A/B testing colors, and dynamic UI changes without class toggling.


Animating with CSS Variables

.progress-bar {
  --progress: 0;
  width: calc(var(--progress) * 1%);
  transition: width 0.3s ease;
}
bar.style.setProperty('--progress', '75'); // animate to 75%

Conclusion

CSS custom properties are one of the most powerful features in modern CSS. Once you start building with a design token system, dark mode becomes trivial, component variants become clean, and runtime theming becomes straightforward. Replace your hardcoded values today.

#css#variables#theming#dark-mode#frontend