Less Mixins Are Just Classes — and That's Both the Point and the Problem
Less mixins reuse plain class syntax, which makes them easy to learn and easy to misuse. Here's how parametric mixins, guards, parentheses, and :extend actually affect your compiled CSS.
06 Feb 2026, 05:02 UTC

You open a stylesheet that's grown to a few thousand lines and find the same five-property button treatment pasted into eleven selectors, each with a slightly different hex value. The obvious fix in Less is a parametric mixin. The less obvious part is deciding how to write it so it doesn't quietly bloat your compiled CSS or leak stray rules into the output. That decision — mixin shape, parentheses, guards, and mixin-versus-:extend — is the real engineering call, and it's what this post is about.
A mixin is a class you call like a function
Less has no special @mixin keyword. Any class or ID selector can be mixed into another rule just by referencing its name. Add parameters and defaults and you get a parametric mixin:
.button-variant(@bg; @color: #fff) {
background-color: @bg;
color: @color;
border: 1px solid darken(@bg, 10%);
border-radius: 4px;
padding: 0.5em 1em;
}
.btn-primary {
.button-variant(#2b6cb0);
}
.btn-danger {
.button-variant(#c53030; #fff);
}Two things to note. First, arguments are separated with semicolons — this is the recommended form because commas are also legal inside CSS values (think font-family stacks), and semicolons avoid ambiguity. Second, the default @color: #fff means the danger button could have omitted its second argument entirely.
The parentheses detail that surprises everyone
Define the mixin as .button-variant(...) — with parentheses — and Less treats it as mixin-only: the definition itself is not emitted into the compiled CSS. Define it as a plain class, .button-variant { ... }, and you get both: the class appears in the output and its properties get copied wherever you mix it in. That stray rule is a common source of "where did this selector come from?" moments in code review.
The practical check: compile a small file and grep the output for your mixin name. If .button-variant shows up as a standalone rule and you didn't intend it as a real utility class, add the parentheses. If you do want it usable as both a class and a mixin, leave them off — but make that a deliberate choice, because it changes what ships to the browser.
Guards: one name, several behaviors
Less mixins support pattern matching through when guards, so a single mixin name can branch on its arguments:
.set-width(@w) when (@w > 500px) {
width: @w;
margin: 0 auto;
}
.set-width(@w) when (@w =< 500px) {
width: 100%;
max-width: @w;
}Calling .set-width(720px) picks the first definition; .set-width(320px) picks the second. This is handy for breakpoints-ish logic or light/dark variants, but keep guards simple. Stacked conditions across many mixin definitions become hard to trace, and guard syntax details have shifted across Less versions — verify against the version pinned in your project's package.json before relying on subtle behavior.
The trade-off: mixins duplicate, :extend shares
Every mixin call copies the full declaration block into the call site. Eleven buttons means eleven copies of padding, border, and radius in your output. Less offers an alternative: :extend merges selectors onto a shared rule instead of duplicating declarations.
.btn-base {
border-radius: 4px;
padding: 0.5em 1em;
}
.btn-primary:extend(.btn-base) {
background-color: #2b6cb0;
}The compiled result groups .btn-base, .btn-primary on one rule — smaller output. So why not always extend? Because :extend doesn't take parameters, behaves awkwardly with nested selectors and media queries, and can produce surprising selector merges. The working heuristic: use parametric mixins when values vary per call site; use :extend when the shared declarations are truly identical. If output size matters to you, compile both approaches on a representative file and compare bytes — it's a five-minute experiment that settles the argument for your codebase.
When to skip the mixin entirely
One honest limitation: for single-value theming — a brand color, a spacing unit — a native CSS custom property (--brand: #2b6cb0) is often the better tool now. Custom properties cascade at runtime, work in every modern browser, and don't require a build step to change. A parametric mixin that only sets one property is preprocessor ceremony around something CSS already does. Reserve mixins for multi-property bundles and logic (guards, computed values like the darken() above) that custom properties can't express.
Also worth knowing before committing a new project: Less's ecosystem momentum has slowed compared to Sass and PostCSS. If you're maintaining an existing Less codebase, mixins remain a solid, well-supported feature. If you're starting fresh, weigh the ecosystem question alongside the syntax.
A concrete starting point
Take one repeated pattern in your stylesheet — the button, the card, the form input — and convert it to a parenthesized parametric mixin with one default argument. Compile with your project's own Less setup (npx lessc styles.less out.css works for a quick check, using whatever Less version the project pins), then inspect out.css for two things: the mixin definition should not appear as its own rule, and each call site should carry the expected declarations. That two-minute inspection catches the two most common mixin mistakes before they reach review.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.