Skip to content

LESS Theme System

Because LESS variables resolve at compile time, building a runtime-switchable theme system requires either compiling multiple stylesheets or bridging LESS tokens into CSS custom properties. This lesson covers both approaches.

Two Approaches to Theming in LESS

The first approach compiles a separate CSS file per theme, each built from the same component styles but different token values, and swaps the linked stylesheet (or uses guarded mixins per theme). The second, more modern approach outputs LESS tokens as CSS custom properties, letting a single compiled stylesheet switch themes live in the browser.

The second approach is generally preferred today because it avoids duplicating compiled CSS and allows instant theme switching without a page reload or a second network request.

// tokens.less
@color-bg-light: #ffffff;
@color-bg-dark: #0f172a;
@color-text-light: #111827;
@color-text-dark: #f1f5f9;

:root {
  --color-bg: @color-bg-light;
  --color-text: @color-text-light;
}

[data-theme="dark"] {
  --color-bg: @color-bg-dark;
  --color-text: @color-text-dark;
}

body {
  background: var(--color-bg);
  color: var(--color-text);
}

LESS variables define the raw palette at compile time; CSS custom properties carry the active theme choice at runtime.

Runtime Theme Switching Pattern

[data-theme="dark"] {
  --color-bg: @color-bg-dark;
}

document.documentElement.setAttribute("data-theme", "dark");
  • Compile LESS token values into CSS custom properties scoped under a data-theme attribute selector.
  • Toggle the theme at runtime with JavaScript by setting the data-theme attribute, no recompilation needed.
  • Component styles reference var(--custom-property), never the raw LESS @variable, so they stay theme-agnostic.
  • This pattern combines LESS's compile-time palette definition with native CSS's runtime flexibility.

LESS Theme System Cheatsheet

Key building blocks for a runtime-switchable theme system.

Building Block Example Purpose
Palette tokens @color-bg-dark: #0f172a; Raw theme-specific values, compile time
Custom property bridge --color-bg: @color-bg-light; Exposes a LESS value at runtime
Theme scope selector [data-theme="dark"] { } Overrides custom properties for a theme
Component usage background: var(--color-bg); Theme-agnostic component styles
Guarded mixin variant .surface() when (@theme = dark) { } Compile-time-only theme alternative

Compile-Time Theming with Guards

For projects that don't need live, in-browser theme switching, a simpler compile-time approach sets a single @theme variable and gates every relevant mixin with a guard, producing one fixed stylesheet per build.

@theme: dark;

.surface() when (@theme = light) { background: white; color: #111; }
.surface() when (@theme = dark)  { background: #111; color: white; }

.card { .surface(); }

Switching themes with this approach requires changing @theme and recompiling, no runtime switching.

Runtime Theming with Custom Properties

Bridging LESS tokens into CSS custom properties, scoped by a data-theme attribute or class, allows a single compiled CSS file to support instant, JavaScript-driven theme switching without recompilation.

document.documentElement.setAttribute(
  "data-theme",
  prefersDark ? "dark" : "light"
);

Choosing the Right Theming Approach

The right approach depends on whether your product needs live theme switching (like a user-toggled dark mode) or a fixed theme decided at build time (like separate branded builds for different clients).

  • Use guarded compile-time theming for build-time brand variants with no runtime switching need.
  • Use the CSS custom property bridge for any user-facing light/dark or preference-based theme toggle.
  • The two approaches can also be combined: guards select a default theme, and custom properties allow runtime overrides.

Common Mistakes

  • Trying to switch a theme at runtime using only LESS @variables, which are already resolved into a static value at compile time.
  • Referencing a raw LESS @color-bg-dark variable directly in component styles instead of the theme-agnostic custom property.
  • Compiling a completely separate CSS bundle per theme when a lighter custom-property bridge would suffice.
  • Forgetting to define a sensible :root fallback for custom properties before any theme-specific override applies.

Key Takeaways

  • LESS variables resolve at compile time; true runtime theme switching requires bridging them into CSS custom properties.
  • A data-theme attribute selector combined with custom property overrides enables instant, JavaScript-driven theme switching.
  • Guarded, compile-time theming works well for fixed, build-time brand variants without live switching.
  • Component styles should reference custom properties, not raw LESS tokens, to stay theme-agnostic.

Pro Tip

Always author component styles against var(--token-name), never the raw @less-variable. This keeps every component automatically theme-aware without needing per-component theme logic.