Skip to content
What Are CSS Custom Properties and How Do They Work?

What Are CSS Custom Properties and How Do They Work?

THE BOTTOM LINE

CSS custom properties allow you to create dynamic, maintainable, and highly flexible styles that update in real time across your entire web layout.

  • Native integration: Unlike preprocessor variables, they run directly in the browser and cascade dynamically through the Document Object Model (DOM).
  • Browser support: They are supported by over 98% of modern browsers, making them safe for production environments.
  • Performance: Localized updates via JavaScript execute in under 16 milliseconds (one frame), enabling highly fluid dynamic styling.

Keep in mind that while custom properties are incredibly versatile, they cannot be used as media query condition values directly.

Why Use CSS Custom Properties?

Managing styles in large stylesheets often leads to repeating identical values. When you define a color palette or spacing system, you might repeat the same hexadecimal values or pixel sizes across hundreds of layout rules. This repetition makes updating the design system a tedious task that requires careful search-and-replace actions across multiple files.

Cascading Style Sheets (CSS) custom properties, also commonly known as CSS variables, solve this problem by introducing native variables to the web platform. They allow you to define a value in a single place and reference it throughout your stylesheets. According to MDN Web Docs, using these properties improves code readability and makes project maintenance far less error-prone.

  • Reduced Redundancy: You define a value once and reuse it across multiple selectors, drastically shrinking the size of your stylesheets.
  • Dynamic Updating: Unlike static preprocessor variables, custom properties exist in the browser at runtime, meaning you can update them dynamically.
  • Improved Semantics: Giving variables clear names like --primary-color makes your styling rules easier to understand than raw values like #00ff00.

How to Declare and Use CSS Variables

To implement variables in your design workflow, you must understand declaring custom properties css and referencing them in your style declarations. The syntax is straightforward, built entirely into standard CSS rulesets, and does not require a compilation step.

Declaring Custom Properties with the — Prefix

You declare a custom property using a double-dash prefix (--) followed by a case-sensitive variable name. The value of this property can be any valid CSS value, such as a color, font size, margin, or animation duration. You place this declaration inside a standard CSS selector to define its scope.

.container {
  --main-bg-color: #0d9488;
  --default-padding: 16px;
}

In this example, the custom properties are declared within the .container scope, making them available to that element and all of its descendants. Note that software versions and browser engines receive frequent updates, so you should always test custom behaviors on target devices.

Referencing Variables with the var() Function

Once you have declared a custom property, you access its value using the var() function. This function acts as a placeholder that the browser replaces with the actual value of the custom property at runtime.

.container {
  background-color: var(--main-bg-color);
  padding: var(--default-padding);
}

You can use the var() function anywhere a standard value is accepted in your CSS properties. You cannot, however, use the function for property names, selectors, or inside media queries as a condition.

Scope and the CSS Cascade

Because CSS custom properties are native to the browser, they follow the same cascade and inheritance rules as other standard CSS properties. This distinguishes them from preprocessor variables, which are compiled away before the browser ever parses the file.

Global Scope with the :root Pseudo-class

To make your variables accessible across your entire stylesheet, you should declare them inside the :root pseudo-class. The :root pseudo-class represents the highest-level parent element in the document, which is the <html> element in standard HyperText Markup Language (HTML) documents.

:root {
  --global-primary: #3b82f6;
  --global-secondary: #10b981;
  --base-font-size: 16px;
}

Declaring variables here ensures that every HTML element in your page can access and use these variables. This approach is highly recommended for defining global design tokens, such as typography, spacing systems, and core brand colors.

Local Scope and Inheritance

If you declare a custom property inside a specific class selector, it is scoped locally. This means only that element and its descendants can inherit and use those values. This localized encapsulation is incredibly useful for building independent, modular components.

.card {
  --card-bg: #ffffff;
  background-color: var(--card-bg);
}

.special-card {
  --card-bg: #f3f4f6;
}

If a child element does not have a local declaration, it will naturally inherit the value of its parent element. This cascading nature makes building responsive themes and component variations clean and intuitive.

Handling Fallbacks and Invalid Values

To ensure your layouts do not break when a variable is missing or invalid, you must understand how CSS processes fallback values. The browser provides built-in mechanisms to handle these edge cases gracefully.

Defining Fallback Values in var()

You can define a fallback value by passing a second argument to the var() function, separated by a comma. The browser will use this fallback value if the referenced custom property has not been declared anywhere in the cascade.

.button {
  background-color: var(--button-theme, #1d4ed8);
}

In this scenario, if the --button-theme property is not defined globally or locally, the browser will render the background color as #1d4ed8. You can also nest multiple fallback values if your design requires several backup layers.

How CSS Handles Invalid Custom Properties

When the browser encounters a custom property reference with an invalid value for that property, it handles the error differently than standard CSS. For example, if you write color: var(--my-size) and --my-size evaluates to 20px, the value is invalid for the color property.

Instead of discarding the declaration and falling back to a previous CSS rule, the browser resets the property to its inherited value or its initial default value. This behavior is called invalid at computed-value time. It is critical to test your stylesheets to ensure invalid values do not cause unintended fallback layouts.

Registering Variables with the @property At-Rule

Modern browser engines allow for advanced custom property declarations using the CSS Properties and Values Application Programming Interface (API). This feature brings a strict structural system to native variable declarations, making your styles more robust and secure.

Benefits of Registering Custom Properties

As documented by web.dev, registering properties using the CSS Properties and Values API provides built-in validation for your styling engines. This mechanism allows developers to build more reliable systems without depending on complex scripts.

  • Strict Type Checking: You can enforce specific data types, such as lengths, percentages, or colors, preventing invalid values from breaking your layouts.
  • Transition and Animation Support: Standard CSS variables cannot be animated directly, but registering them allows browsers to interpolate their values during transitions.
  • Inheritance Control: You can explicitly define whether a custom property cascades down to descendant elements or remains isolated.

Defining Types, Initial Values, and Inheritance

You register a variable using the @property at-rule directly inside your stylesheet. You must define the syntax type, specify if the variable inherits, and set an initial fallback value.

@property --brand-accent {
  syntax: "<color>";
  inherits: false;
  initial-value: #f59e0b;
}

This explicit declaration guarantees that --brand-accent only accepts valid color values. If a developer accidentally assigns a length value like 10px to this variable, the browser will reject it and apply the initial color instead.

Practical Use Cases for CSS Variables

Using CSS variables effectively goes beyond simple color replacement. Understanding how to use css variables in complex contexts allows you to write highly interactive and responsive components with minimal code.

Theme Switching (Dark Mode)

Building a light and dark theme is incredibly simple with custom properties. Instead of writing separate stylesheets, you can define your core theme colors as variables and change their values using a data attribute or class on the body element.

:root {
  --bg-primary: #ffffff;
  --text-primary: #1f2937;
}

[data-theme="dark"] {
  --bg-primary: #111827;
  --text-primary: #f9fafb;
}

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
}

This architecture allows you to change the global color scheme of your website instantly by toggling a single attribute in your HTML, with no need to reload the page or re-evaluate layout trees.

Responsive Design and Media Queries

You can update the value of your custom properties inside media queries to adapt your spacing, typography, and grids to different screen dimensions. This keeps your media queries clean and highly readable.

:root {
  --main-padding: 16px;
  --header-font-size: 24px;
}

@media (min-width: 768px) {
  :root {
    --main-padding: 32px;
    --header-font-size: 36px;
  }
}

Every element using these properties automatically scales its dimensions based on the active viewport width, without you needing to write repetitive media query rules for every single class.

Simplifying Animations and Transitions

You can animate custom properties to create complex visual transitions without cluttering your code. This is particularly powerful when combining properties with standard CSS transforms.

.animated-box {
  --box-scale: 1;
  transform: scale(var(--box-scale));
  transition: transform 0.3s ease;
}

.animated-box:hover {
  --box-scale: 1.1;
}

This layout technique reduces the amount of code required to create micro-interactions, making it easier to build engaging user interfaces.

Accessing and Modifying Custom Properties in JavaScript

One of the biggest strengths of CSS custom properties is their accessibility through JavaScript. You can read, modify, or delete these values programmatically at runtime to create highly dynamic interactions.

To access the calculated value of a custom property on any DOM element, use the getComputedStyle() method in conjunction with the getPropertyValue() method. This returns the exact value of the property parsed by the rendering engine.

const rootElement = document.documentElement;
const primaryColor = getComputedStyle(rootElement).getPropertyValue('--global-primary').trim();
console.log(primaryColor);

To change a value dynamically, use the setProperty() method on the element’s style object. This dynamic update instantly propagates through the entire cascade, modifying all components referencing that variable.

rootElement.style.setProperty('--global-primary', '#ef4444');

This API allows you to build features like user-controlled custom themes, dynamic color pickers, or mouse-tracking layouts with minimal performance overhead.

CSS Custom Properties vs. Preprocessor Variables

Web developers have relied on preprocessors like Sass and Less for variables for over a decade. While both modern CSS custom properties and preprocessor variables serve similar purposes, they operate on completely different architectures.

Feature CSS Custom Properties Preprocessor Variables
Evaluation Time Runtime (in the browser) Compilation time (build step)
DOM Awareness Yes, respects inheritance and cascade No, compiled into flat static values
JS Integration Can be read and written directly Inaccessible to JavaScript
Media Queries Values can change within media queries Static values cannot change inside media queries

Understanding these fundamental architectural differences helps web designers choose the optimal toolset for their development workflows. Most modern frameworks use both, using preprocessor variables for build-time calculations and custom properties for dynamic, responsive tokens.

Browser Support and Compatibility

When implementing styling technologies, verifying browser compatibility ensures a consistent user experience. In 2026, browser engines have achieved highly stable support for all core custom property features.

Engine / Browser Core Custom Properties @property Registration
Google Chrome Full Support Full Support
Mozilla Firefox Full Support Full Support
Apple Safari Full Support Full Support
Microsoft Edge Full Support Full Support

Before deploying complex animation workflows utilizing registered custom properties, you should check modern testing suites to ensure older mobile environments fallback gracefully. For production systems, defining reliable fallback values inside your var() calls remains an essential best practice.