Home / Articles / Color & Theming /

color

CSS custom properties (variables)

Learn how custom properties unlock dynamic theming, component tokens, and design-system flexibility that preprocessor variables never could.

Declaring and using var()

Custom properties are declared with a double-hyphen prefix and consumed via var(). They cascade and inherit just like any other CSS property.

:root {
  --brand: oklch(65% 0.25 275);
  --radius: 0.5rem;
}

.card {
  background: var(--brand);
  border-radius: var(--radius);
}

Fallback values

var() accepts a second argument as a fallback. You can even nest var() calls for multi-level defaults.

.btn {
  /* falls back to --accent, then to a literal color */
  color: var(--btn-color, var(--accent, oklch(70% 0.18 145)));
}
Fallbacks only apply when the property is not set or is initial. An invalid value like --x: 42px used as a color will not trigger the fallback.

Inheritance and scoping

Custom properties inherit by default. Scope them to a component by declaring on a class instead of :root.

.card {
  --card-pad: 1.5rem;
  padding: var(--card-pad);
}

.card--compact {
  --card-pad: 0.75rem; /* children inherit the override */
}

Typed variables with @property

The @property rule gives a custom property a type, initial value, and inheritance control. This enables transitions and guards against invalid values.

@property --hue {
  syntax: "<number>";
  inherits: false;
  initial-value: 270;
}

.swatch {
  background: oklch(65% 0.2 var(--hue));
  transition: --hue 0.4s ease;
}

.swatch:hover {
  --hue: 145;
}

Component token pattern

Expose a small API of custom properties on each component. Consumers restyle without touching internals.

.badge {
  --badge-bg: oklch(92% 0.04 260);
  --badge-text: oklch(30% 0.08 260);
  background: var(--badge-bg);
  color: var(--badge-text);
  padding: 0.2em 0.6em;
  border-radius: 999px;
}

/* theme override — no specificity war */
.dark .badge {
  --badge-bg: oklch(30% 0.08 260);
  --badge-text: oklch(92% 0.04 260);
}
This pattern is the backbone of every modern design-system. Keep the token list small and document it alongside the component.

Performance considerations

Changing a custom property on :root triggers style recalculation for the entire page. For frequent updates, scope the property to the smallest possible subtree.

  • Prefer component-scoped properties over global ones when only part of the UI changes.
  • Use @property with inherits: false to prevent unnecessary propagation.
  • Combine with @layer to keep override specificity flat.