Skip to content
--custom-propertyIntermediateUsed in 40 effects

Themeable effects with CSS custom properties

Custom properties store values in the cascade. Unlike preprocessor variables, they survive into the browser, inherit through the document, respond to media queries, and can be changed for one component instance without regenerating a stylesheet.

A good custom property is a small public API: its name explains intent, its scope matches the component that owns it, and its fallback keeps the declaration valid when no override exists.

Use the cascade as the configuration system

Declare broad design tokens on :root only when they truly belong to the entire application. Put component-specific controls on the component selector so names do not become accidental global contracts.

Custom property names are case-sensitive. --accent and --Accent are different values. A reference can include a fallback, and that fallback can itself contain another var() call.

Design token
A shared decision such as a color, spacing step, radius, or motion duration.
Component property
A local override point that belongs to one component contract.
Fallback
The second argument of var(), used when the referenced property is missing or invalid.
cssGlobal tokens and a local component API
:root {
  --color-accent: #60a5fa;
  --radius-control: .75rem;
}

.button {
  --button-bg: var(--color-accent);
  --button-fg: #08111f;
  color: var(--button-fg, white);
  background: var(--button-bg, #2563eb);
  border-radius: var(--radius-control);
}

.button--danger { --button-bg: #fb7185; }

Scope overrides near the element that owns them

Inheritance makes theme wrappers powerful: set a token on a subtree and every descendant that references it adapts. The same behavior can also create surprises when generic names leak too far.

Use names such as --card-border rather than --border for component contracts. Reserve short, broad names for intentionally global systems.

Register values that need interpolation

Unregistered custom properties are token streams, so browsers switch many of them discretely during animation. @property supplies a syntax, inheritance rule, and initial value, allowing numeric colors, lengths, angles, and percentages to interpolate.

Registration is global by name. Choose stable prefixed names in reusable components and make the initial value valid for the declared syntax.

cssAnimate a gradient input, not the gradient string
@property --progress {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

.meter {
  background: conic-gradient(#38bdf8 var(--progress), #1e293b 0);
  transition: --progress 500ms ease;
}
.meter.is-complete { --progress: 100%; }

Update values without coupling to presentation

JavaScript can call element.style.setProperty('--progress', '72%') while CSS remains responsible for color, easing, and layout. This keeps state transfer small and avoids constructing whole style strings in application code.

Validate values before writing them and preserve semantic state in attributes or application data. A custom property is presentation input, not the only record of business state.

Live catalog examples

These production examples use --custom-property. Open any effect to inspect its complete source and experiment in the browser editor.

Browser support

Support notes refer to stable, unprefixed behavior unless a prefix is explicitly listed.

FeatureBrowsersNote
Custom properties / var()All modern browsersSupported across the modern baseline.
@propertyModern Chromium, Firefox, SafariProvide a static result when interpolation is optional.
CSS.registerProperty()Modern browsers with supportThe stylesheet @property form is usually easier to audit.

Common pitfalls

  • Names differ only by letter case

    Custom property names are case-sensitive; use one consistent naming convention.

  • An unregistered value is expected to interpolate

    Register its syntax with @property when smooth numeric animation is required.

  • No fallback is supplied

    Add a meaningful second argument to var() when the property may be absent.

  • Every component value is placed on :root

    Scope local contracts to their component and keep only true design tokens global.

Frequently asked questions

Are custom properties the same as Sass variables?
No. They participate in the runtime cascade, inherit, and can change in the browser after CSS is generated.
Can JavaScript update them?
Yes. Use style.setProperty on an element or a suitable ancestor.
Why is my variable fallback not used?
Fallbacks apply when the custom property is missing or invalid at substitution time, not merely because its value looks inconvenient.
When should I use @property?
Use it when you need typed validation, controlled inheritance, an initial value, or smooth interpolation.

Related guides

Last updated:

All guides