← Back to CSS3 Mastery
Intermediate13 min read

CSS Variables

Use custom properties (CSS variables) for theming, dynamic values, and maintainable design tokens that cascade and respond to JavaScript.

Defining Custom Properties

CSS custom properties are declared with double dashes and accessed with var(). Unlike preprocessor variables, they are live in the browser — values can change at runtime and cascade like any other property.

Define global tokens on :root and component-scoped tokens on specific selectors. This creates a token hierarchy that mirrors your design system.

  • Custom properties inherit — child elements see parent values
  • They are case-sensitive: --Color is different from --color
  • Use semantic names (--color-text-muted) not literal ones (--gray-500)
:root {
  --color-primary: #2563eb;
  --color-surface: #ffffff;
  --space-md: 1rem;
  --radius-md: 8px;
  --font-body: system-ui, sans-serif;
}

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

Scope and Inheritance

Custom properties follow the cascade. A variable defined on a parent is available to all descendants. Redefining a variable on a child creates a new scope without affecting siblings.

This scoping makes theming straightforward — set --color-surface on a dark container and all nested components automatically use dark surface colors.

[data-theme="dark"] {
  --color-surface: #1e293b;
  --color-text: #f1f5f9;
  --color-primary: #60a5fa;
}

.card {
  background: var(--color-surface);
  color: var(--color-text);
}

Fallback Values

The var() function accepts a fallback as the second argument: var(--undefined, red). This prevents broken styles when a variable is missing.

Use @property to register custom properties with a syntax, initial-value, and inherits flag. Registered properties can be animated and validated by the browser.

@property --progress {
  syntax: '<percentage>';
  initial-value: 0%;
  inherits: false;
}

.ring {
  background: conic-gradient(
    var(--color-primary) var(--progress),
    #e2e8f0 var(--progress)
  );
  transition: --progress 500ms ease;
}

JavaScript Integration

JavaScript can read and write CSS variables via getComputedStyle and element.style.setProperty. This enables dynamic theming, animation control, and data-driven styling without inline style proliferation.

Reading variables from :root gives you the global token values. Writing to element.style.setProperty updates only that element and its descendants.

  • Use setProperty on document.documentElement for global theme switches
  • CSS variables work with calc() for dynamic computed values
  • Prefer CSS variables over JS inline styles for maintainability
// Read a variable
const primary = getComputedStyle(document.documentElement)
  .getPropertyValue('--color-primary');

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

Design Token Architecture

Organize tokens in layers: primitive tokens (raw values), semantic tokens (purpose-based names), and component tokens (component-specific overrides). This three-tier system scales from small projects to enterprise design systems.

Tools like Style Dictionary can generate CSS custom properties from a single JSON token source, keeping design and code in sync.

:root {
  /* Primitives */
  --blue-600: #2563eb;
  /* Semantic */
  --color-interactive: var(--blue-600);
  /* Component */
  --button-bg: var(--color-interactive);
}

Get In Touch


Ready to discuss your next project? Drop me a message.