Skip to content

Less Variables and Scope — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Less Variables and Scope. We cover key concepts, practical examples, and best practices to help you master this topic.

Less variables use lazy evaluation and block scope where the last declaration wins, differing from Sass eager evaluation for dynamic value assignment.

What You'll Learn

  • Variable declaration with @
  • Lazy evaluation vs eager evaluation
  • Block scope in Less
  • Variable interpolation for selectors and properties
  • Variable arguments in mixins
  • Imported variable behavior

Why It Matters

  • Lazy evaluation affects variable override order
  • Understanding scope prevents unexpected values
  • Interpolation enables dynamic selectors
  • Imported variables are global by default

Real-World Use

  • A theme file overrides variables after definition
  • A dynamic selector uses interpolated variables
  • A mixin modifies variables inside a block
  • An imported library variable is overridden
flowchart LR
  A[Less Variables] --> B[Lazy Evaluation]
  A --> C[Block Scope]
  A --> D[Interpolation]
  B --> E[Last definition wins]
  C --> F[Scope within blocks]
  D --> G[@{selector}, @{property}]

Variables in Less

Code Example: Lazy Evaluation

// Lazy evaluation: the last value assigned is used
// Even if used before the assignment

@primary: #0066CC;

.element-1 {
    background: @primary;  // Will this be #0066CC or #ff6600?
}

@primary: #ff6600;  // Last declaration wins!

.element-2 {
    background: @primary;  // #ff6600
}

// Both .element-1 and .element-2 will be #ff6600
// because Less waits until all declarations are processed,
// then uses the final value

// Compare with Sass (eager evaluation)
// $primary: #0066CC;
// .element-1 { background: $primary; }  // #0066CC (eager)
// $primary: #ff6600;
// .element-2 { background: $primary; }  // #ff6600

Expected output: In Less, both .element-1 and .element-2 use #ff6600 because the last declaration of @primary takes effect. In Sass, .element-1 would use #0066CC (eager evaluation).

Code Example: Block Scope

// Global scope
@primary: #0066CC;
@spacing: 16px;

.card {
    // Block scope: local variable overrides global
    @primary: #ff6600;  // Overrides global @primary only inside this block
    @card-padding: 24px;  // Local scope (not accessible outside)

    background: @primary;  // #ff6600
    padding: @card-padding;  // 24px
}

.other {
    background: @primary;  // #0066CC (global value, unaffected)
    // padding: @card-padding;  // ERROR: undefined
}

// Nested scope
.header {
    @text-color: #333;

    &__title {
        @text-color: #0066CC;  // Overrides parent scope
        color: @text-color;  // #0066CC
    }

    &__subtitle {
        color: @text-color;  // #333 (parent scope unaffected)
    }
}

Expected output: Block-scoped variables override global values only within their block. Nested blocks can override parent scope variables. This scoping is more intuitive than Sass's global scope for variables.

Code Example: Variable Interpolation

// Interpolation for selectors
@prefix: 'app';
@component: 'card';
@state: 'featured';

.@{component} {
    background: #fff;

    // Dynamic element name
    &__@{state} {
        border-color: #0066CC;
    }
}

// Interpolation for properties
@property: margin;
@direction: left;
@value: 16px;

.element {
    @{property}-@{direction}: @value;  // margin-left: 16px
}

// Interpolation in mixin names
.mixin-selector(@name) {
    .@{name} {
        color: #0066CC;
    }
}

.mixin-selector('alert');      // Generates .alert { color: #0066CC; }
.mixin-selector('badge');      // Generates .badge { color: #0066CC; }

// Interpolation in media queries
@breakpoint: 768px;

@media (min-width: @breakpoint) {
    .container {
        max-width: 720px;
    }
}

// Variable in variable (indirection)
@color-primary: #0066CC;
@theme-var: 'color-primary';

.element {
    // @@ evaluates to @color-primary
    color: @@theme-var;  // #0066CC
}

Expected output: Interpolation with @{} enables dynamic selectors, property names, and values. The @@ syntax provides variable indirection (variable variable names).

Code Example: Imported Variables

// _variables.less
@primary: #0066CC;
@secondary: #6c757d;
@font-stack: 'Inter', system-ui, sans-serif;

// _theme.less - overrides
@primary: #ff6600;  // Overrides the imported default
// This works because of lazy evaluation - last @primary wins

// main.less
@import 'variables';    // @primary = #0066CC
@import 'theme';        // @primary = #ff6600 (overrides)

.element {
    color: @primary;    // #ff6600
}

// Multiple imports
// Variables from imports are global by default
// There is no @use-like namespacing in Less
// With !important-like behavior (Less does not have !default)
// Override pattern: define after import
@import 'variables';

// Override specific variables
@primary: #7c3aed;  // Purple instead of blue
@secondary: #6b7280;

Expected output: Imported variables are global. Later imports override earlier ones due to lazy evaluation. Less does not have the !default flag that Sass has, but the same effect is achieved by importing overrides after defaults.

Common Mistakes

  1. Expecting Sass-like eager evaluation — If you come from Sass, Less lazy evaluation will surprise you. Variables use the last declared value, not the value at the time of use.
  2. Not understanding block scope — Block-scoped variables do not leak outside. Accessing a block-scoped variable from outside causes an error.
  3. Forgetting @{} interpolation syntax — Without interpolation, @selector is treated as a property value, not a selector name.
  4. Overriding variables unintentionally — Import order matters. A later import can silently override earlier imported variables.
  5. Using @@ variable indirection unnecessarily — @@ is rarely needed. It makes code harder to read.
  6. Not using variable arguments correctly — @arguments in Less captures all mixin parameters as a single variable.
  7. Assuming CSS custom property behavior — Less variables are compile-time. They are not the same as CSS custom properties (var()).

Practice Questions

  1. What is lazy evaluation in Less? Variables use the last assigned value throughout the file, regardless of declaration order.
  2. How does block scope work in Less? Variables declared inside a block override outer variables only within that block.
  3. What interpolation syntax does Less use for selectors? @{selector-name} to use a variable as a selector.
  4. How do you override an imported variable in Less? Import the defaults first, then declare the override value after the import.

FAQ

Does Less have a !default equivalent?

No. The pattern is to import defaults first, then override: @import 'variables'; @primary: my-value;.

Can I detect if a variable is defined in Less?

Less does not have a built-in variable-exists function like Sass. Use conditional mixins with guards instead.

How do I create private variables?

Less does not have true private variables. Use naming conventions (e.g., @_private-var) and avoid importing internal files.

Mini Project

Create a Less project that demonstrates variable behavior. Create: (1) a _variables.less file with color and spacing defaults, (2) a _theme-overrides.less that changes some defaults, (3) a component file that uses both imported and local block-scoped variables, (4) a demonstration of lazy evaluation (using a variable before it is defined), (5) interpolation for selectors and properties, (6) nested block scoping showing how parent and child scopes interact. Compile and verify the output matches the expected behavior. Write comments explaining each variable resolution.

What's Next

Continue with Lesson 21: Less Mixins for advanced mixin patterns in Less.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro