Skip to content

Less Language Mixins

Mária Jurčovičová edited this page Jan 17, 2016 · 93 revisions

Standard css does not have an easy way to say: I want this ruleset to be just like that one, with this simple modification. If you need multiple rulesets with the same margin and padding, then you have to copy both margin and padding into all of them. When you decide to change the shared part, then you have to change it on all those copied places.

Mixins are solution to all these "code repetition" problems. Mixin is named group of css properties (declarations). You can include itinto a ruleset or another structure and all its properties are going to be copied there.

Less mixins are quite powerful:

  • They can be parametrized and parameters syntax is surprisingly flexible.
  • Each mixin can have a guard - an entry condition. The mixin will be used only if the condition is satisfied.
  • Each mixin can be marked as !important.
  • Each mixin can have an entry pattern - an old version of guards which will be deprecated in the future.

Each of these features is described in a separate section. In addition, last two sections of the mixins chapter deal with scoping and rulesets nesting inside mixins. If you are new to less, make sure to read the scoping section. Less treatment of the scope is a bit unusual and you can avoid some surprises this way.

Syntax Overview

Mixin declaration has four parts: name, parameters, guard condition and body. Parameters and guard condition are optional and mixin without them is also a ruleset.

Naming

The name must start either with a dot '.' or a hash '#' and must be without whitespaces or special symbols other then a dot '.' or a hash '#'. Otherwise said, mixins name must be composed of valid css class names or element ids. Valid names: .mixin, #id123, .mixin.extended, #id123.something#98.

This naming convention has one exception: mixins that are also rulesets can have also selector combinators in their names. Selector combinators in these mixins are ignored. For example, ruleset with .parent > .child selector can be also used as a mixin.

Declaring Mixins

Both parameters and guard condition are optional. The body is enclosed in curly braces '{}' and contains a list of declarations.

  • name(parameters) when (guard condition) { declaration: value; }

Calling Mixins

Place mixins name wherever you want to include its properties and nested rulesets. The name can be followed by parentheses with list of parameters and by the !important keyword. It must end with semicolon ';'.

  • name(parameters) !important;

Note: the discussions about what kind of mixins names should and should not be allowed are still going on. See: https://github.com/cloudhead/less.js/issues/1048

Simple Example

Declare and use simple mixin:

// declare mixin
.name() { declaration: value; }
// use mixin inside ruleset
body {
  .name();
}

compiles into:

body {
  declaration: value;
}

Mixin Without Parameters

Simplest possible mixin has no parameters and no guard condition. Since there are no parameters, the parentheses after its name is optional. Mixin declaration without parentheses acts as both ruleset and mixin. It can be included into other structures and its properties are going to be copied there, exactly the same way as mixin with parentheses. As it is also a ruleset, its declaration is also copied into final css.

Mixin declaration with parentheses acts as mixin only. It can be called, but its declaration is never copied into the output.

  • .mixin() {...} - mixin declaration is removed and does not appear in the final css,
  • .mixin {...} - mixin is also a ruleset and will be copied into the output.

Empty parentheses can be omitted also when calling mixins. Calls .mixin(); and .mixin; are equivalent and there is no difference between them.

Create and use two mixins without parameters:

.mixin() {
  color: #33acfe;
}
.mixinNoParentheses {
  margin: 2 2 2 2;
}
.some .selector div {
  .mixin;
  .mixinNoParentheses ();
}

compiles into:

.mixinNoParentheses {
  margin: 2 2 2 2;
}
.some .selector div {
  color: #33acfe;
  margin: 2 2 2 2;
}

Rulesets As Mixins

Ruleset can have a selector combinator in its name and still be used as a mixin. The selector combinator in such mixin is not considered to be part of its name and is ignored. Therefore, all following rulesets:

.parent > .child { declaration: value; }
.parent .child { declaration: value; }
.parent ~ .child { declaration: value; }
.parent.child { declaration: value; }

are matched by all following mixin calls:

.parent > .child;
.parent .child
.parent.child

Name Conflicts

It is legal to declare multiple mixins with the same name. Later definition does not redefine previous one, all of them are valid. Less will join all their properties into one big group:

.mixin() {
  color: #33acfe;
}
.mixin() {
  margin: 2 2 2 2;
}
.some .selector div {
  .mixin;
}

compiles into:

.some .selector div {
  color: #33acfe;
  margin: 2 2 2 2;
}

Return Values

Variables and mixins defined inside mixins act as return values and are usable in caller. Returned variables never rewrite callers local variables. Only variables not present in callers local scope are going to be copied there.

Variable defined in mixin acts as return value:

.mixin() {
  @size: in-mixin; 
  @definedOnlyInMixin: in-mixin;
}

.class {
  @size: localy-defined-value; //local declaration - protected
  margin: @size @definedOnlyInMixin;
  .mixin(); 
}

Compiles into:

.class {
  margin: localy-defined-value in-mixin;
}

Only local variables are protected. Variables inherited from parent scopes are overridden:

.mixin() {
  @size: in-mixin; 
  @definedOnlyInMixin: in-mixin;
}

.class {
  margin: @size @definedOnlyInMixin;
  .mixin(); 
}

@size: globaly-defined-value; // non-local declaration - irrelevant

Compiles into:

.class {
  margin: in-mixin in-mixin;
}

Unlocking Mixins

Mixin defined in mixin acts as return value too:

.unlock(@value) { // outer mixin
  .doSomething() { // nested mixin
    declaration: @value;
  }
}

#namespace {
  .unlock(5); // unlock doSomething mixin
  .doSomething(); //nested mixin was copied here and is usable 
}

#use-namespace { 
  #namespace > .doSomething(); // it works also with namespaces
}

compiles into:

#namespace {
  declaration: 5;
}
#use-namespace {
  declaration: 5;
}

The outer 'unlocking' mixin must be called first. This would throw syntax error:

.doSomething(); // syntax error: nested mixin is not available yet
.unlock(5); // too late

Imported mixins act as if they have been defined at the place where they have been unlocked:

.unlock() { // outer mixin
  .doSomething() { // nested mixin
    imported: imported;
  }
}

.selector {
  .doSomething() { 
    first: first;
  }
  .unlock(); // unlock doSomething mixin
  .doSomething() { 
    last: last;
  }
  //all three mixins are called 
  .doSomething(); 
}

results in:

.selector {
  first: first;
  imported: imported;
  last: last;
}

Nested Mixins

Nested mixin can have leading combinator or appender. The declaration of .mixin inside the #theme is valid:

#theme {
  // declare mixin
  > .mixin {
    background-color: grey;
  }
  //use it
  .mixin();
}

compiles into:

#theme {
  background-color: grey;
}
#theme > .mixin {
  background-color: grey;
}

Nested mixins are not allowed to have appender on any other place then beginning. This is illegal:

#nested {
  .nested & {
    declaration: nested;
  } 

  .nested(); // illegal line - .nested is undefined
}

Mixins With Rulesets

Nesting rulesets into mixins is allowed. Nested rulesets are placed into the caller the same way as all mixins property declarations.

Example input:

.mixin() {
  color: #33acfe;
  //ruleset nested inside mixin
  nested ruleset {
    margin: 2 2 2 2;
  }
}
div {
  .mixin;
}

compiles into:

div {
  color: #33acfe;
}
div nested ruleset {
  margin: 2 2 2 2;
}

If the mixin contains only nested rulesets and no property declaration, then it can be called from the style sheet top level:

.mixin() {
  //ruleset nested inside mixin
  nested ruleset {
    margin: 2 2 2 2;
  }
}
//call the mixin from style sheet top level
.mixin;

compiles into:

nested ruleset {
  margin: 2 2 2 2;
}

Parameters

This section describes how to deal with mixins parameters. It is split into multiple sub-sections:

  • explicite parameters,
  • named parameters,
  • list expansion in mixin call,
  • special collector parameter,
  • default all arguments variable.

Explicit Parameters

Parameters do not have types and act as local variables inside the mixin. It is possible to give them default value and thus declare them as optional. Of course, if some parameter has a default value, then all following parameters must have one too. Parameters are either semicolon or comma separated and look like ordinary variables:

  • without default values: .name(@param1; @param2; ...; param),
  • with default value: .name(@param1: red; @param2: 4; ...; param: "http://www.google.com").

It is recommended to use semicolon. The symbol comma has double meaning: it can be interpreted either as a mixin parameters separator or CSS list separator. Using comma as mixin separator makes it impossible to create comma separated lists as an argument.

Semicolon does not have such limitation. If the compiler sees at least one semicolon inside mixin call or declaration, it assumes that arguments are separated by semicolons. All commas then belong to css lists. For example:

  • two arguments and each contains comma separated list: .name(1, 2, 3; something, else),
  • three arguments and each contains one number: .name(1, 2, 3),
  • use dummy semicolon to create mixin call with one argument containing comma separated css list: .name(1, 2, 3;),
  • comma separated default value: .name(@param1: red, blue;).

Declare and use mixin with three parameters. Last parameter is optional:

.mixin(@color; @padding; @margin: 2, 2, 2, 2) {
  color: @color;
  padding: @padding;
  margin: @margin;
}
.some .selector div {
  .mixin(#33acfe; 4);
}

compiles into:

.some .selector div {
  color: #33acfe;
  padding: 4;
  margin: 2, 2, 2, 2;
}

As before, it is legal to define multiple mixins with the same name and number of parameters. Less will join properties of all that can apply. If you used the mixin with one parameter e.g. .mixin(green);, then properties of all mixins with exactly one mandatory parameter will be used:

.mixin(@color) {
  color-1: @color;
}
.mixin(@color; @padding:2) {
  color-2: @color;
  padding: @padding;
}
.mixin(@color; @padding; @margin: 2) {
  color-3: @color;
  padding: @padding;
  margin: @margin @margin @margin @margin;
}
.some .selector div {
  .mixin(#008000);
}

compiles into:

.some .selector div {
  color-1: #008000;
  color-2: #008000;
  padding: 2;
}

Named Parameters

A mixin reference can supply parameters values by their names instead of just positions. Any parameter can be referenced by its name and they do not have to be in any special order:

.mixin(@color: black; @padding: 2; @margin: 2) {
  ...
}
.some .selector div {
  //use named parameters to call the mixin
  .mixin(@margin:4; @color: #33acfe);
}

If reference contains both positional and named parameters, less assigns named parameters first. Unnamed parameters are then used to fill values for all remaining arguments starting at the left:

Input:

.mixin(@a:defA; @b:defB; @c:defC; @d:defD) {
  arguments: @a, @b, @c, @d;
}
.some .selector div {
  .mixin(1; @a:a; 2);
}

Output:

.some .selector div {
  arguments: a, 1, 2, defD;
}

List Expansion In Mixin Call

A mixin reference can expand a list content into individual mixin arguments. Each list member will fill one parameter:

.mixin(@a, @b, @c, @d) { //define mixin
  parameter-a: @a;
  parameter-b: @b;
  parameter-c: @c;
  parameter-d: @d;
}

.ruleset {
  // values inside @list will expand into arguments b, c, d
  .mixin(value-of-a, @list...); 
  @list: 1, 2, 3;
}

Compiles into:

.ruleset {
  parameter-a: value-of-a;
  parameter-b: 1;
  parameter-c: 2;
  parameter-d: 3;
}

Tip: list expansion can be used for comfortable mixin looping:

.loop() { the: end; }
.loop(@head, @tail...) {
  value: @head;
  .loop(@tail...);
}

.class {
  @values : 1 2 3 4;
  .loop(@values...);
}

compiles into:

.class {
  value: 1;
  value: 2;
  value: 3;
  value: 4;
  the: end;
}

Collector Parameter

Collector parameter collects all unmatched unnamed parameters. It must be used as a last mixin parameter. A mixin with a collector parameter accepts arbitrary big number of parameters and ignores named parameters. Collector parameter is denoted using three dots ... and can be either named or unnamed:

Named collector parameter:

.mixin(@collected...) {
  declaration: @collected;
}
.mixin(@a; @collected...) {
  declaration: @collected;
}
#collector {
  .mixin(0; 1; 2; 3; 'end');
}

result:

#collector {
  declaration: 0 1 2 3 'end';
  declaration: 1 2 3 'end';
}

Unnamed collector parameter:

.mixin(...) {
  declaration: value;
}
.mixin(@a; ...) {
  declaration: @a;
}
#collector {
  .mixin(0; 1; 2; 3; 'end');
}

result:

#collector {
  declaration: value;
  declaration: 0;
}

Default @arguments Variable

All mixins arguments are collected in a list and stored inside the @arguments variable. Every mixin with at least one parameter has the @arguments variable available.

@arguments used together with unnamed collector parameter:

.mixin(...) {
  declaration: @arguments;
}
.mixin(@a; ...) {
  declaration: @a @arguments;
}
#collector {
  .mixin(0; 1; 2; 3; 'end');
}

result:

#collector {
  declaration: 0 1 2 3 'end';
  declaration: 0 0 1 2 3 'end';
}

Note: the @arguments variable name is not reserved. You can create custom parameter or variable with the same name. The default @arguments will not be available anymore, custom one takes precedence:

.mixin(...) {
@arguments: 10;
  declaration: @arguments;
}
.mixin(@a; @b; @arguments...) {
  declaration: @arguments;
}
#collector {
  .mixin(0; 1; 2; 3; 'end');
}

result:

#collector {
  declaration: 10;
  declaration: 2 3 'end';
}

!important

Mixin can be used together with the !important keyword. If you place !important after the mixin call, all properties included by it will be marked as important.

Sample input:

.mixin (@a: 0) {
  border: @a;
  boxer: @a;
}
.unimportant {
  .mixin(1); 
}
.important {
  .mixin(2) !important; 
}

compiled into:

.unimportant {
  border: 1;
  boxer: 1;
}
.important {
  border: 2 !important;
  boxer: 2 !important;
}

Guards

A guard contains condition attached to a mixin. Each mixin can have multiple guards separated by comma ',' and condition inside the guard must be enclosed in parentheses .mixin(...) when guard1, guard2, ..., guardn. A mixin with multiple guards is used only if at least one guard is satisfied. Otherwise said, the comma does 'or'.

Mixin with two guards, each guard have one condition:

.mixin(@a; @b) when (@a<10),(@b<10) {
  declaration: @a @b;
}
#guarded {
  .mixin(100; 1);
}

result:

#guarded {
  declaration: 100 1;
}

Comparison Operators

Less language contains five comparison operators: '>', '>=', '=', '=<' and '<'. The equality comparator can compare numbers, strings, colors and identifiers. It is unable to compare lists. Remaining four operators work only with numbers.

Units are meaningful. All operators convert numbers into the same unit before comparing them. If it is not possible, for example if left side is in px and right side is in %, then the operator returns false. Number without explicit type can be compared to anything.

Example - comparing numbers:

.mixin(@var) when (@var<10%) {
  declare: @var is less then 10%;
}
.mixin(@var) when (@var<5cm) {
  declare: @var is less then 5cm;
}
#no-unit {
  .mixin(4);
}
#conversion {
  .mixin(40mm);
}

result:

#no-unit {
  declare: 4 is less then 10%;
  declare: 4 is less then 5cm;
}
#conversion {
  declare: 40mm is less then 5cm;
}

Example - incompatible units:

.mixin() when (10mm > 1deg) {
  wont-print: guard is false;
}
.mixin() when (10mm < 1deg) {
  wont-print: guard is false;
}
.mixin() when (10mm = 1deg) {
  wont-print: guard is false;
}
.mixin() {
  whatever: whatever;
}
#incompatible {
  .mixin();
}

result:

#incompatible {
  whatever: whatever;
}

The 'true' Keyword

Less contains the keyword 'true'. True is case sensitive and is the only non-boolean value that evaluates as true:

.mixin(@var) when (true) {
  declare: @var;
}
.mixin(@var) when (45) {
  declare: fail;
}
#tryNumber {
  .mixin(value);
}

result:

#tryNumber {
  declare: value;
}

Boolean Expressions

Guards also support parenthesis and logical operators not, and and or. Conditional statements are evaluated the same way as in javascript or java. E.g. unless parenthesis is used, negation is evaluated first, and goes second and or is evaluated as third.

Less supports three boolean operators not, and and or:

.mixin(@var) when (@var<10) and (@var>4) {
  declare: @var is lower then 10 and higher then 4
}
.mixin(@var) when not (@var>10) and not (@var<4) {
  declare: @var is NOT higher then 10 and NOT lower then 4
}
.mixin(@var) when (@var>10) or not (@var<4) {
  declare: @var is either higher then 10 or NOT lower then 4
}
#tryNumber {
  .mixin(5);
}

result:

#tryNumber {
  declare: 5 is lower then 10 and higher then 4;
  declare: 5 is NOT higher then 10 and NOT lower then 4;
  declare: 5 is either higher then 10 or NOT lower then 4;
}

Important: all three logical operators inside the guard take precedence over the comma ',' between guards:

.orImportance () when (true), (false) and (false) { 
  content: guard was evaluated first;
}
#orImportance { .orImportance; }

result:

#orImportance {
   content: guard was evaluated first;
}

Default Function

Special default() function can be used to create default mixins. It can be used only inside mixin guards and returns true only if no other mixin matches mixin call. The default function can be combined with other guard functions or logical operators (and, not). If the call matched at least one mixin whose guards are satisfied and which does not use the default() function, then default() returns false. The situation is slightly more complicated if there is no such mixin:

  • If exactly one mixin matches the call and that one mixin uses the default() function, then the default function returns true.
  • If multiple mixins match the call and all of them use the default() function, then Less does not know what to do. The situation is considered ambiguous and Less returns an error.

Simple example:

.mixin(@size) when (@size=big) { //each size type has special style
  color: red;
}
.mixin(@size) when (default()) { //fallback style to be used only if nothing else is defined
  color: blue;
}
.selector-1 {
  .mixin(unexpected); // will use fallback style
}
.selector-2 {
  .mixin(big); // will NOT use fallback style
}

compiles into:

.selector-1 {
  color: blue;
}
.selector-2 {
  color: red;
}

You can combine default with other guard conditions. Following:

.mixin(@width) when (default()) and (@width>25) {
  color: blue;
}
.mixin(@width) when (default()) and (@width=<25) {
  color: red;
}
.mixin(@width) when (@width=10) {
  color: white;
}
.selector-1 {
  .mixin(30); //will use the first mixin; default() is true
}
.selector-2 {
  .mixin(10); //will use third mixin; default() is false
}

compiles into:

.selector-1 {
  color: blue;
}
.selector-2 {
  color: white;
}

Negative default example:

.mixin(@width) when (@width=10) {
  color: white;
}
.mixin(@width) when not(default()) {
  padding: 2 2 2 2;
}
.selector-1 {
  .mixin(10); //will use both mixins
}
.selector-2 {
  .mixin(15); //neither mixin will be used
}

compiles into:

.selector-1 {
  color: white;
  padding: 2 2 2 2;
}

Ambiguous situations - negated default:

.wrong(@width) when not(default()) {
  color: white;
}
.wrong(@width) when not(default()) {
  color: red;
}
.selector-1 {
  .wrong(10); //ERROR: only multiple negative default functions active at the same time
}

Ambiguous situations - default and not-default:

.wrong(@width) when (default()) {
  color: white;
}
.wrong(@width) when (not(default())) {
  color: red;
}
.wrong(@width) when (@width<10) { //the guard condition is not satisfied, so the mixin is NOT active. The situation is considered ambiguous and error is thrown.
  color: red;
}
.selector-1 {
  .wrong(10); //ERROR: two default functions active at the same time
}

However, if the condition in third mixin would be satisfied, the less would compile. The value for default() would be clearly false:

.wrong(@width) when (default()) {
  color: white;
}
.wrong(@width) when (not(default())) {
  size: 2;
}
.wrong(@width) when (@width=10) { //the guard condition is satisfied, so the mixin is active. The situation is NOT ambiguous and less file will be compiled.
  color: red;
}
.selector-1 {
  color: red;
}

compiles into:

.selector-1 {
  size: 2;
  color: red;
}

Boolean Functions

Less has multiple built-in boolean functions able to determine value type. Use them to make sure that mixin parameter has the right type. This is especially useful in combination with guards comparison operators, because those can not be used with non-numbers.

List available of boolean functions:

  • iscolor,
  • isruleset,
  • iskeyword,
  • isnumber,
  • isstring,
  • ispixel,
  • ispercentage,
  • isurl,
  • isem.

Example:

//make sure that operator < is used only if the variable holds a number
.mixin(@var) when (isnumber(@var)) and (@var<10%) { 
  declare: passed and;
}

Pattern Matching

Pattern matching is guards predecessor. Its purpose is similar: turn on and off mixins depending on some condition. Any parameter in mixin declaration can be replaced by a pattern. A pattern must be a simple expression: an identifier, a string, a color or a number. Pattern empowered mixin is used the same way as regular mixin. Less then compares supplied values against declared patterns and use it only if they match.

Mixin declaration, first and third parameters have been replaced by patterns:

.mixin(pattern; @a; 4px) { 
  declaration: @a;
}

With the exception of colors, a required and supplied patterns must match exactly. This is major difference against how guards have been treated. In particular:

  • quote type around the string must match,
  • number suffix and type must match.

Example, quote type:

.mixin(...) { 
  //catch all mixin to avoid "No matching definition" compiler complains
}
.mixin(@a; 'value') { 
  declaration: single quote;
}
.mixin(@a; "value") { 
  declaration: double quote;
}
#singleQuote { 
  .mixin(2; 'value'); 
}
#doubleQuote { 
  .mixin(2; "value"); 
}

result:

#singleQuote {
  declaration: single quote;
}
#doubleQuote {
  declaration: double quote;
}

Example, number suffix:

.mixin(...) { 
  //catch all mixin to avoid "No matching definition" compiler complains
}
.mixin(@a; 4px) { 
  declaration: @a;
}
#rightPattern { 
  .mixin(2; 4px); 
}
#wrongPattern { 
  .mixin(2; 4%); 
}

result:

#rightPattern {
  declaration: 2;
}

Colors are a bit different, pattern match colors by value and ignores the way it was supplied:

.mixin(red) {
  declare: red;
}
.mixin(#ff0000) {
  declare: #ff0000;
}
#colorByName {
  .mixin(red);
}
#colorByCode {
  .mixin(#ff0000);
}

result:

#colorByName {
  declare: red;
  declare: #ff0000;
}
#colorByCode {
  declare: red;
  declare: #ff0000;
}

Note: pattern in mixin declaration must be written exactly. You can not use expressions to calculate patterns. The following mixin definition is incorrect:

.mixin(1+2){
  //not a valid mixin
}

Scoping and Validity

Less is a declarative language and that has important consequences on where and how are mixins valid. Mixins are valid within the whole scope where they have been defined. Whole means anywhere - even before they have been being declared.

Mixin can be used before being declared:

.some .selector div {
  .mixin;
}
.mixin() {
  color: #33acfe;
}

compiles into:

.some .selector div {
  color: #33acfe;
}

When less looks for a mixin to be used, it looks into the smallest possible scope first. Only names are considered, parameters number and guard conditions are ignored. It looks into higher level scopes only if no mixin with the same name is found. Once less found a scope with at least one same name mixin, it will stop the scope search. The next step is to eliminate all mixins with wrong number of parameters or unsatisfied guard conditions. Only the properties of remaining mixins are joined and replace the mixin call.

Mixins defined inside smaller scopes overshadows mixins defined in higher scopes:

.mixin() {
  color: #33acfe;
}
.some .selector div {
  .mixin() {
    padding: 2;
  }
  .mixin;
}

compiles into:

.some .selector div {
  padding: 2;
}

Mixins defined inside smaller scopes overshadow mixins defined in higher scopes:

.mixin() {
  color: #33acfe;
}
.some .selector div {
  .mixin(@size) {
    padding: @size;
  }
  .mixin;
}

compiles into:

.some .selector div {
  //empty, the only mixin withing the same scope does not match
}

Variables and Mixins Accessible Inside the Mixin

Any mixin can use all variables and mixins accessible on place where it is called. Both definition and caller scopes are available to it. If both scopes contains the same variable or mixin, declaration scope value takes precedence.

Mixin can use variables and mixins declared in the caller scope:

.mixin() {
  declaration: @callerVariable;
  .callerMixin();
}

selector {
  @callcallerVariableerScope: 10;
  .callerMixin() {
    variable: declaration;
  }
  .mixin();
}

is compiled into:

selector {
  declaration: 10;
  variable: declaration;
}

Declaration scope value takes precedence:

.mixin() {
  @scope: 5;
  declaration: @scope;
}

selector {
  @scope: 10;
  .mixin();
}

is compiled into:

selector {
  declaration: 5;
}

Non-local declaration scope takes precedence too:

@scope: 5;
.mixin() {
  declaration: @scope;
}

selector {
  @scope: 10;
  .mixin();
}

compiles into:

selector {
  declaration: 5;
}

Variables and Mixins Accessible Inside Unlocked Mixin

Both variables and mixins returned by other mixins are available inside unlocked mixins:

.defineSpecialTheme() { 
  @theme: special; 
  .themeMixin() {
    special-theme-mixin: running;
  }
}

.unlock() {
  .showTheme() {  
    theme-used-by-unlocked-mixin: @theme;
    .themeMixin();
  }
}

#namespace {
  .unlock();
  .defineSpecialTheme(); // return @theme variable and unlock .themeMixin mixin
  .showTheme(); 
}

compiles into:

#namespace {
  theme-used-by-unlocked-mixin: special;
  special-theme-mixin: running;
}
Clone this wiki locally