CSS Custom Properties (Variables): A Practical Guide

CSS custom properties — often called CSS variables — let you define reusable values in one place and reference them throughout your stylesheet. Unlike preprocessor variables (Sass/LESS), they are live in the browser: JavaScript can read and write them, and they respect the cascade and inheritance.

Problem: How do you define reusable values in CSS — such as brand colours or spacing units — so you can update them in one place and have the change propagate everywhere?

Solution: Use CSS custom properties — declare them on :root with the -- prefix and read them with var(). Unlike preprocessor variables, they cascade and can be overridden per element at runtime without a build step.

Define variables on the :root element to make them globally available:

:root {
    --color-primary:   #3498db;
    --color-secondary: #2ecc71;
    --color-text:      #333;
    --font-base:       'Open Sans', sans-serif;
    --spacing-sm:       0.5rem;
    --spacing-md:       1rem;
    --spacing-lg:       2rem;
    --radius:           4px;
}

.btn {
    background: var( --color-primary );
    color: #fff;
    padding: var( --spacing-sm ) var( --spacing-md );
    border-radius: var( --radius );
    font-family: var( --font-base );
}

.btn:hover {
    /* Fallback value if the variable is not defined */
    background: var( --color-hover, #2980b9 );
}

Override variables in a scoped context — great for themes and dark mode:

.theme-dark {
    --color-primary: #1a1a2e;
    --color-text:    #e0e0e0;
}

/* All components inside .theme-dark automatically pick up the new values */

Read and update variables from JavaScript:

// Read
const primary = getComputedStyle( document.documentElement )
    .getPropertyValue( '--color-primary' ).trim();

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

NOTE: CSS custom properties are case-sensitive: --Color-Primary and --color-primary are different variables. Browser support for custom properties has been solid since early 2017 — Edge 15, Chrome 49, Firefox 31, Safari 9.1 all support them. For IE11 support, you'll need a polyfill or a PostCSS transform.