What is CSS variables (custom properties)?

CSS variables (custom properties): CSS variables, or custom properties, are author-defined properties named with two leading dashes whose values you reuse with var() and that inherit.

A custom property is any property whose name starts with --. You declare it like any other property and read it with var(). Unlike Sass or Less variables, which are replaced at build time, custom properties are live in the browser: they follow the cascade, inherit down the DOM tree, can be overridden in a selector or media query, and can be changed from JavaScript at runtime. That makes them the natural carrier for design tokens, themes and any value that depends on context.

How it works

css
:root {
  --accent: oklch(0.53 0.215 283);
  --radius: 0.5rem;
}
.dark {
  --accent: oklch(0.7 0.165 285);
}
.button {
  background: var(--accent);
  border-radius: var(--radius, 6px);
}
  • Scope follows the cascade. A value declared on :root applies everywhere; one declared on .card applies inside cards and overrides the root value there.
  • Fallbacks. var(--radius, 6px) uses 6px when --radius is not defined.
  • Resolution is late. The browser substitutes the value where it is used, at computed-value time, so calc(var(--space) * 2) works and updates when --space changes.
  • Names are case sensitive. --Accent and --accent are different properties.
  • JavaScript access. el.style.setProperty("--lx", "120px") writes one; getComputedStyle(el).getPropertyValue("--lx") reads it.

Common pitfalls

An unregistered custom property is untyped, so the browser cannot interpolate it: transitioning --angle from 0deg to 90deg jumps instead of animating. Register it with @property and a syntax such as "<angle>" to make it animatable.

A reference to an undefined variable with no fallback makes the whole declaration invalid at computed-value time. The property then falls back to its inherited or initial value, not to an earlier declaration in your stylesheet, which surprises people who expect normal CSS error recovery. Finally, changing variables on :root every frame forces style recalculation across the whole page, so scope rapidly updated values to the smallest subtree that needs them.

How MiniDev UI uses them

Every color, shadow and radius in MiniDev UI is a custom property, and Tailwind CSS v4 maps them to utilities through @theme. Dark mode and the four materials only redefine variables, never components. LightProvider goes further: it writes --lx, --ly, --sx, --sy and --la on <html> from the pointer position, and every shadow in the kit reads them, so all shadows fall away from one light. The box shadow generator and the guide to shadows from one light source show the technique in detail.

Light ProvideruiA page wide light source that follows the pointer and drifts when idle, driving shadows via CSS variables, plus a hairline, glass, metal and paper switcher.npx shadcn@latest add ui.minidev.pro/r/light-provider.json