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.

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.

What You'll Learn

💡 Think of It Like This

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.

1. Declaring & Using a Variable

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.

Concept

Syntax

Example

Declare (global)

:root { --name: value; }

--primary: #3b82f6;

Declare (scoped)

.card { --pad: 20px; }

Only inside .card

Use

var(--name)

color: var(--primary);

Use with fallback

var(--name, fallback)

var(--accent, #f59e0b)

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.

2. Fallbacks — A Safety Net for var()

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.

3. Scope, Inheritance & the Cascade

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.

4. Theming — Swap One Attribute

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.

5. Reading & Setting Variables in JavaScript

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.

🎯 Your Turn #1 — Declare and use a variable

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.

🎯 Your Turn #2 — Add a fallback and scope an override

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.

🧩 Mini-Challenge — Theme tokens from scratch

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.

⚠️ Common Errors (and the fix)

📋 Quick Reference

Goal

Use this

Declare a global variable

:root { --primary: #3b82f6; }

Declare a scoped variable

Use a variable

Use with a fallback

Override per-component

.danger { --primary: #ef4444; }

Theme by attribute

[data-theme="dark"] { --bg: #0f172a; }

Set from JavaScript

el.style.setProperty('--hue', '120')

Read from JavaScript

getComputedStyle(el).getPropertyValue('--hue')

❓ Frequently Asked Questions

🎉 Lesson Complete

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.

Continue this course

Related lessons