Skip to content

LESS and CSS Custom Properties

LESS @variables and native CSS custom properties (--name) solve a similar problem in fundamentally different ways. This lesson clarifies the distinction and shows practical patterns for using both together.

LESS Variables vs CSS Custom Properties

A LESS variable is a compile-time construct: the compiler substitutes its value everywhere it's used, and the variable itself never appears in the output CSS. A CSS custom property is a runtime construct: it's an actual CSS declaration (--name: value;) that the browser resolves live, and can be read, overridden, or changed dynamically via JavaScript or media queries.

These aren't competing features, they're complementary. LESS variables are excellent for values that never need to change once compiled; custom properties are essential for anything that should be adjustable at runtime, like theming.

@spacing-md: 1rem; // compile-time, resolved once

:root {
  --spacing-md: @spacing-md; // bridges the LESS value into a runtime custom property
}

.card {
  padding: var(--spacing-md); // resolved live, in the browser
}

@spacing-md never appears in the output CSS; --spacing-md does, carrying the resolved value forward into the runtime.

Bridging Syntax

:root {
  --custom-property: @less-variable;
}
  • LESS substitutes @less-variable's resolved value directly into the custom property declaration.
  • Once declared, --custom-property behaves like any other CSS custom property, readable and overridable at runtime.
  • This bridging pattern is the foundation of the theme system covered earlier in this course.
  • Custom properties can be overridden per-selector, per-media-query, or via JavaScript, none of which LESS variables support after compilation.

LESS Variables vs Custom Properties Cheatsheet

A direct comparison of the two mechanisms.

Aspect LESS @variable CSS Custom Property
Resolved At compile time At runtime, in the browser
Appears in output CSS? No, fully substituted away Yes, as a live declaration
Can change after page load? No Yes, via JS or media queries
Cascades and inherits? No, purely a compiler construct Yes, like any CSS property
Best for Fixed build-time values, math, logic Runtime theming, user preferences

Using LESS to Generate Many Custom Properties at Once

Combining each() with a map-like detached ruleset lets LESS generate an entire set of custom properties from a single token definition, avoiding repetitive manual declarations.

@tokens: {
  spacing-sm: 0.5rem;
  spacing-md: 1rem;
  spacing-lg: 2rem;
}

:root {
  each(@tokens, {
    --@{key}: @value;
  });
}

Math and Guards Still Require LESS Variables

Custom properties can be used inside calc(), but they can't participate in LESS's compile-time arithmetic, guards, or color functions directly. Any math, conditional logic, or derived color needs to happen with LESS variables first, with the final result exposed as a custom property if runtime access is needed.

@primary: #2563eb;
@primary-hover: darken(@primary, 8%); // LESS computes this at compile time

:root {
  --primary: @primary;
  --primary-hover: @primary-hover; // the computed result, exposed at runtime
}

Deciding Which One to Use

A simple rule of thumb: if a value needs LESS's compile-time logic (math, guards, color functions) to compute, do that in LESS first; if a value needs to change after the page loads, expose the final result as a custom property.

  • Use LESS variables for intermediate computation, math, guards, derived colors.
  • Use custom properties for the final, runtime-facing values components actually consume.
  • It's common, and often ideal, for both to be used together in the same project.

Common Mistakes

  • Trying to perform LESS-style arithmetic or color functions directly on a CSS custom property; these only work on LESS @variables at compile time.
  • Assuming a LESS variable can be changed at runtime via JavaScript the way a custom property can.
  • Forgetting to bridge a computed LESS value into a custom property when a component actually needs runtime access to it.
  • Manually writing dozens of repetitive custom property declarations instead of generating them with each() from a token map.

Key Takeaways

  • LESS variables resolve at compile time and never appear in the output CSS; custom properties are live, runtime CSS declarations.
  • Bridge a LESS variable into a custom property with --name: @variable; to expose a compile-time value at runtime.
  • Compile-time logic (math, guards, color functions) must happen in LESS; only the final result should become a custom property.
  • Use each() over a token map to generate many custom properties at once, avoiding repetitive manual declarations.

Pro Tip

Treat LESS variables as your compile-time computation layer and custom properties as your runtime consumption layer: compute and derive values with LESS, then expose only what actually needs runtime flexibility as a --custom-property.