Many users expect variables defined within an :extend() block to override the variable values in the extended selector. However, this does not work as expected:
@color: red;
a {
color: @color;
}
.foo {
&:extend(a all);
@color: green; // ❌ This does NOT make .foo a green
}Expected Output:
.foo a { color: green; }Actual Output:
a,
.foo a {
color: red;
}Less.js compiles in two distinct phases:
- All variables are resolved to their final values
- Expressions are evaluated
- Mixins are expanded
a { color: @color; }becomesa { color: red; }- The
@color: green;in.foois scoped to.fooonly and doesn't affecta
- The
:extend()visitor runs AFTER evaluation - It only duplicates/rewrites selectors
- No re-evaluation of values occurs
.foo ais created by copying the already-evaluated declarations froma
Key Point: Extend works on selectors, not on values. Variable scope is resolved during evaluation, before extend runs.
Changing this behavior would require:
- Re-evaluating declarations under new variable scopes after extend
- Potentially breaking existing codebases that rely on current behavior
- Significant performance impact
- Complex interactions with other features (mixins, imports, etc.)
The current behavior is consistent with Less's compilation model.
Use a mixin with parameters to create reusable styles that can be customized per context.
@color: red;
// Define a reusable mixin with a parameter
.a-style(@c: @color) {
color: @c;
}
// Apply with default color
a {
.a-style();
}
// Apply with custom color
.foo a {
.a-style(green);
}Output:
a {
color: red;
}
.foo a {
color: green;
}Advantages:
- Clean and maintainable
- Explicit customization points
- Works with all Less features
- No duplication of logic
When to use: When you need to share styles with different variable values across multiple contexts.
Leverage native CSS variable cascading instead of Less variables.
a {
color: var(--a-color, red);
}
.foo {
--a-color: green;
}Output:
a {
color: var(--a-color, red);
}
.foo {
--a-color: green;
}The browser will apply --a-color: green to .foo a through CSS cascade.
Advantages:
- Uses native CSS features
- Works at runtime (can be changed with JavaScript)
- No compilation complexity
- Modern and future-proof
When to use: When you need runtime variable changes or are building a theme system.
Explicitly re-declare properties where you need different values.
@color: red;
a {
color: @color;
}
.foo {
&:extend(a all);
}
.foo a {
@color: green;
color: @color;
}Output:
a,
.foo a {
color: red;
}
.foo a {
color: green;
}CSS cascade will apply the second rule to .foo a.
Advantages:
- Simple and straightforward
- Works with existing code
- Clear intent
Disadvantages:
- Some duplication
- Specificity considerations
When to use: When you have only a few overrides and want simplicity.
Combine mixins and extend for shared base styles with customizable parts.
// Define base styles
.base-styles() {
border: 1px solid black;
padding: 10px;
}
// Apply base to element a
a {
.base-styles();
color: red;
}
// Extend for shared styles, override color
.foo a {
&:extend(a all);
color: green;
}Output:
a,
.foo a {
border: 1px solid black;
padding: 10px;
color: red;
}
.foo a {
color: green;
}When to use: When you have many shared styles but only a few properties need customization.
| Solution | Complexity | Flexibility | Performance | Best For |
|---|---|---|---|---|
| Parameterized Mixin | Low | High | Good | Shared component styles |
| CSS Custom Properties | Low | Very High | Excellent | Theming, runtime changes |
| Explicit Override | Very Low | Low | Good | Simple one-off cases |
| Mixin + Extend Hybrid | Medium | Medium | Good | Partial customization |
.foo {
&:extend(a all);
@color: green; // This won't work
}.parent {
@color: blue;
.child {
&:extend(a all); // @color: blue won't apply to extended styles
}
}.parent {
@color: blue;
.child {
.a-style(@color); // Explicit parameter passing
}
}A: No, this would require fundamental changes to Less's architecture and would be a breaking change.
A: No, this is expected behavior based on Less's two-phase compilation (eval, then extend).
A: Use a parameterized mixin or CSS custom properties. Both scale well.
A: Sass's @extend works similarly - it operates on already-evaluated selectors. However, Sass has different scoping rules for variables.
A: No! Extend is powerful for sharing selector groups. Just use mixins for value customization.
Let's build a button system:
// Base button styles (shared)
.btn-base {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
// Color mixin (customizable)
.btn-color(@bg, @text) {
background-color: @bg;
color: @text;
&:hover {
background-color: darken(@bg, 10%);
}
}
// Primary button
.btn-primary {
&:extend(.btn-base all);
.btn-color(#007bff, white);
}
// Success button
.btn-success {
&:extend(.btn-base all);
.btn-color(#28a745, white);
}
// Custom button in .foo context
.foo .btn-primary {
.btn-color(#ff6b6b, white); // Different color scheme
}This pattern:
- Shares base styles via extend (no duplication)
- Customizes colors via mixin (flexible)
- Works correctly in all contexts
:extend()works on selectors, not values- Variables are resolved before extend runs
- Use parameterized mixins for value customization
- Use CSS custom properties for runtime theming
- This is by design, not a bug
- Less.js Documentation - Extend
- Less.js Documentation - Mixins
- CSS Custom Properties (MDN)
- GitHub Issue #3706
Last Updated: October 19, 2025
Less.js Version: 4.4.2+