Custom Properties

CSS custom properties (also called CSS variables) are reusable, named values you define with a --name prefix and read back with var() ; they cascade and inherit like normal CSS, so changing one value can re-theme an entire page.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll be able to define live CSS variables, theme a whole page by changing one value, and read or update those variables from JavaScript — the foundation every modern design system is built on.

A custom property is like a paint swatch labelled at the hardware store. Instead of writing "Pantone 2728 C" on every wall in the plans, you label one swatch --brand-blue and point at that label everywhere.

When the client changes their mind, you repaint one swatch and every room updates by itself — no hunting through forty walls. That single point of change is the whole idea, and because the swatch is a live label (not a one-time copy), you can even swap it while the paint is still wet: that is what lets JavaScript and themes change your colours after the page has loaded.

A custom property (the official name for a "CSS variable") is any property whose name starts with two dashes: --name: value; . You read it back anywhere a value is expected with the var(--name) function. The name is case-sensitive, so --Primary and --primary are two different properties.

Run the worked example. Every colour, radius, and spacing value comes from one block of variables at the top. Change a single value there and watch the whole design move with it.

var() takes an optional second argument: a value to use when the variable is undefined or out of scope. So color: var(--accent, #f59e0b); means "use --accent if it exists, otherwise fall back to amber." This is what stops a typo or a missing variable from leaving an element completely unstyled.

It matters because a var() that resolves to nothing makes the whole declaration invalid — the browser throws it away, and your element loses that style entirely. A fallback turns a silent disappearance into a sensible default.

Custom properties are not magic globals — they obey the same rules as every other CSS property. A variable is scoped to the selector it's declared on and inherits down to that element's descendants. Declare it on :root (which matches html ) and it's available to the whole page. Declare it on .card and only .card and what's inside it can see that value.

Because they follow the cascade, you can override a variable lower down the tree: a component can redefine --primary for itself without touching the global one. The value is resolved where it's used , so the same var(--primary) can produce different results in different parts of the page.

Theming is the payoff. Define one set of variables for the light theme and another for the dark theme, keyed off an attribute like data-theme on the html element. Every component already reads var(--bg) , var(--text) and friends, so flipping that one attribute re-themes the entire page — no per-element JavaScript, no duplicated rules.

Because custom properties are live , JavaScript can read and change them at runtime — something Sass variables can never do. To set one: element.style.setProperty('--name', value) . To read the resolved value: getComputedStyle(el).getPropertyValue('--name') .

Set the variable on document.documentElement (the html element, i.e. :root ) and it changes globally, because :root variables inherit everywhere. Every rule that uses var(--name) updates the instant you set it.

The colours below are hard-coded three times. Pull them into a variable so a single change re-themes the card. Fill in the blanks marked ___ , then run it and check the expected result in the comments.

One element uses a variable that was never declared, and one component needs its own colour. Add a var() fallback and a scoped override to fix both, then verify the expected colours in the comments.

Support is faded now — only an outline is given. Build a small page that uses a variable design system, with one scoped override. Use the worked examples in sections 1 and 3 as your reference if you get stuck.

You can now build maintainable, themeable CSS with custom properties. The essentials:

Next up: Web Accessibility , where you'll make every page usable for keyboard and screen-reader users.

Practice quiz

How do you declare a CSS custom property?

  • $name: value;
  • @name: value;
  • --name: value;
  • var-name: value;

Answer: --name: value;. Custom properties start with two dashes: --name: value;

How do you read back a custom property's value?

  • var(--name)
  • get(--name)
  • value(--name)
  • $(--name)

Answer: var(--name). The var() function substitutes a custom property's value: var(--name).

What does the second argument in var(--accent, #f59e0b) do?

  • Sets the variable
  • Defines a second variable
  • Specifies the unit
  • Acts as a fallback if --accent is undefined

Answer: Acts as a fallback if --accent is undefined. The second argument is a fallback used when the variable is undefined or out of scope.

Where should you declare site-wide global variables?

  • body
  • :root
  • *
  • @global

Answer: :root. :root matches the <html> element, so variables declared there inherit across the whole document.

What happens if var(--accent) resolves to nothing and has no fallback?

  • The whole declaration becomes invalid and is ignored
  • The element uses black
  • The browser throws an error
  • It uses the parent's value

Answer: The whole declaration becomes invalid and is ignored. A var() that resolves to nothing makes the declaration invalid, so the browser discards that style.

Are custom property names case-sensitive?

  • No, --Primary equals --primary
  • Only in Firefox
  • Yes, --Primary and --primary are different
  • Only inside :root

Answer: Yes, --Primary and --primary are different. Custom property names are case-sensitive: --Primary and --primary are two distinct properties.

If you declare --pad on .card, where is that variable available?

  • The whole document
  • Only .card and its descendants
  • Only the .card element itself
  • Every sibling of .card

Answer: Only .card and its descendants. A variable is scoped to its selector and inherits down to that element's descendants only.

How do you set a custom property from JavaScript?

  • element.setVar('--name', value)
  • element.css('--name', value)
  • element.variable('--name', value)
  • element.style.setProperty('--name', value)

Answer: element.style.setProperty('--name', value). element.style.setProperty('--name', value) sets a custom property at runtime.

What is a key difference between a CSS custom property and a Sass variable?

  • Sass variables inherit; CSS ones don't
  • CSS custom properties are live and changeable at runtime
  • They are identical
  • Sass variables work in JavaScript

Answer: CSS custom properties are live and changeable at runtime. CSS custom properties are live values the browser keeps and JS can change; Sass variables are compiled away.

Which is a valid use of a custom property?

  • @media (--mobile)
  • var(--selector) { ... }
  • margin: var(--space-md)
  • --name as a property name

Answer: margin: var(--space-md). var() only substitutes inside a property's value, so margin: var(--space-md) is valid.