CSS: Variables & Functions

Store reusable values with custom properties and perform dynamic calculations with CSS functions.

Custom Properties (--variable-name)

CSS custom properties, also called CSS variables, let you store a value under a name that starts with --. You can then reuse that value anywhere by referencing the name. Changing the value in one place updates every element that uses it.

Custom Properties

Custom properties are defined with a double-dash prefix (--). They are case-sensitive, inherit down the DOM tree, and can hold any valid CSS value.

  • --variable-name: value: definition syntax; the double dash is required
  • Names are case-sensitive: --Color and --color are different variables
  • Can hold any CSS value: colours, lengths, font stacks, even partial values
  • :root: the conventional place for global variables; equivalent to the html element but with higher specificity
  • Custom properties cascade and inherit just like regular CSS properties

Custom properties

Both cards use the same --radius variable. Change it once and both update.

var() Function

var() reads the value of a custom property. You pass the variable name as the first argument. The browser resolves it at the point of use and inserts the stored value.

var()

var() is the only way to read a custom property's value in CSS. It can be used wherever a regular CSS value would be accepted.

  • var(--name): reads the value of the custom property --name
  • var(--name, fallback): uses fallback if --name is not defined or invalid
  • var() can be nested: var(--a, var(--b, blue))
  • var() cannot be used as a property name, only as a value
  • If the variable resolves to an invalid value for the property, the property becomes its initial value

var()

The colour is stored in a variable and read with var().

Variable Scope (Global vs Local)

Custom properties follow the CSS cascade. A variable defined on :root is available everywhere. A variable defined on a specific element is available only to that element and its descendants, this is local scope.

Variable Scope

Local variables override global ones for the element they are defined on and its children. This lets you create component-level themes without affecting the rest of the page.

  • global: defined on :root; available to every element on the page
  • local: defined on a specific element; only available within that subtree
  • A local variable with the same name as a global one overrides the global within its scope
  • This is how component theming works: define --bg, --color etc. on the component root

Global vs local scope

The second card defines a local --card-bg that overrides the global one.

Fallback Values

The second argument to var() is a fallback value. If the variable is not defined or is invalid, the fallback is used instead. This makes your CSS more resilient and self-documenting.

Fallback values

Fallback values are used when a variable is not defined in the current scope. They do not guard against invalid types, if --size is 'hello', font-size falls back to its initial value, not the var() fallback.

  • var(--color, steelblue): uses steelblue if --color is not defined
  • var(--size, 1rem): uses 1rem if --size is not defined
  • var(--a, var(--b, red)): nested fallback: try --a, then --b, then red
  • Fallback values can be any valid CSS value including other var() calls
  • Document your component variables with meaningful fallbacks so they work without a theme

Fallback values

The second box has no --bg variable so the fallback colour is used.

Updating Variables with JavaScript

CSS variables can be read and updated from JavaScript using setProperty and getPropertyValue. This makes them powerful for dynamic theming, user preferences, and interactive effects.

JavaScript + CSS variables

Updating a CSS variable via JavaScript instantly re-renders every element that depends on it, far more efficient than updating each element's style individually.

  • element.style.setProperty('--name', value): sets a local variable on the element
  • document.documentElement.style.setProperty('--name', value): sets a global variable on :root
  • getComputedStyle(el).getPropertyValue('--name'): reads the current value of a variable
  • Updating a variable on :root updates every element using it simultaneously
  • This is the standard technique for dark/light mode toggling and dynamic themes

Updating via JavaScript

The box colour is set dynamically by JavaScript writing to the CSS variable.

calc()

calc() performs mathematical calculations in CSS. Its key power is mixing different units, something impossible with plain values. The browser evaluates the expression at render time.

calc()

calc() supports +, -, *, and /. Spaces are required around + and - operators. Multiplication and division do not require spaces but it is good practice to include them.

  • calc(100% - 32px): full width minus 32px of padding
  • calc(50vw - 1rem): half the viewport width minus 1rem
  • calc(var(--base) * 2): double a CSS variable's value
  • Spaces are required around + and - operators: calc(100% -32px) is invalid
  • Can be nested: calc(calc(100% / 3) - 10px)

calc()

Main area fills exactly the remaining width using calc(), no JavaScript needed.

min(), max(), clamp()

These three functions choose between values based on what is smallest, largest, or within a range. They are especially useful for responsive sizing without media queries.

min(), max(), clamp()

These functions accept a comma-separated list of values and return one of them based on which is smallest, largest, or within bounds. Each argument can be any valid CSS length, including calc() expressions.

  • min(50%, 400px): uses whichever is smaller: 50% of the container or 400px
  • max(200px, 30%): uses whichever is larger: 200px or 30% of the container
  • clamp(min, preferred, max): preferred value clamped between min and max
  • clamp(1rem, 3vw, 2rem): font scales with viewport, never below 1rem or above 2rem
  • clamp() removes the need for font-size media queries in most cases

min()

Box is 80% wide on small screens but caps at 400px on large ones.

clamp()

Font sizes scale fluidly between a minimum and maximum using clamp().

attr() and url()

attr() reads an HTML attribute value and inserts it as CSS content. url() references an external resource such as an image or font file.

attr() and url()

attr() currently only works reliably inside the content property of pseudo-elements. url() is used for background images, list-style-image, and @font-face src.

  • attr(href): inserts the element's href attribute value as text
  • attr(data-label): inserts a custom data attribute value
  • url('image.png'): references an image file for background-image or list-style-image
  • url('font.woff2'): references a font file inside @font-face src
  • attr() only works as a value for the content property in current browser implementations

attr() in content

The href value is appended to each link automatically using attr().

Variable Naming Conventions

Custom property names are flexible, you can use any characters after the double dash. Good naming conventions make variables self-explanatory and easy to maintain across a large codebase.

Naming Conventions

There is no enforced standard, but consistent naming conventions dramatically improve readability. The most common patterns are category prefixes and semantic names.

  • --color-primary, --color-text: category prefix (color-, spacing-, font-) groups related variables
  • --btn-bg, --btn-color: component prefix scopes variables to a component
  • --size-sm, --size-md, --size-lg: scale-based naming for spacing and sizing systems
  • Use kebab-case (hyphens): --font-size-base not --fontSizeBase
  • Prefer semantic names: --color-danger over --color-red (the meaning, not the value)
  • Document variables with a comment above the :root block in larger projects

Naming conventions

A structured set of CSS variables using category prefixes, a common pattern in design systems.

Knowledge Check

1. What is the correct syntax to define a CSS custom property?

2. Where should you define global CSS variables to make them available everywhere?

3. What does var(--color, red) do if --color is not defined?

4. What does calc(100% - 40px) calculate?

5. What does clamp(1rem, 3vw, 2rem) return when 3vw equals 1.5rem?

6. How do you update a CSS variable from JavaScript?