Less CSS: Mixins with Variable Number of Arguments

Less CSS: Mixins with Variable Number of Arguments

See my answer here: Multiple properties are getting treated as separate arguments in mixins

Summary: use this mixin for variable number of arguments:

.transition (@value1,@value2:X,...)
{
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;

-webkit-transition: @value;
-moz-transition: @value;
-ms-transition: @value;
-o-transition: @value;
transition: @value;
}

Variable list of arguments in a LESS mixin

@rest is not a keyword, its a sample identifier use to demonstate .... It could have any other name.

To fix your code:

.linear-gradient(@rest...) {
background: -webkit-linear-gradient(@rest);
background: -o-linear-gradient(@rest);
background: -moz-linear-gradient(@rest);
background: linear-gradient(@rest);
}

body > header {
.linear-gradient(red, yellow, blue);
}

You could replace @rest with @arguments inside the mixin as you use the entire input as a "variable arguments list". Then you could also omit the @rest identifier altogether:

.linear-gradient(...) {
background: -webkit-linear-gradient(@arguments);
background: -o-linear-gradient(@arguments);
background: -moz-linear-gradient(@arguments);
background: linear-gradient(@arguments);
}

There's no difference between the 2 versions.

But all of that is not what you want at all. The 2 solutions above will result in a CSS that looks like this:

background: linear-gradient(red yellow blue);

That is because the values are interpreted as individual arguments, and are concatenated in a string with spaces.

What you need is this:

.linear-gradient(@colors) {
background: -webkit-linear-gradient(@colors);
background: -o-linear-gradient(@colors);
background: -moz-linear-gradient(@colors);
background: linear-gradient(@colors);
}

body > header {
.linear-gradient(red, yellow, blue;);
}

This tells LESS that the argument passed into the mixin is a single variable, with comma's inside. That translates to your required output.

Less CSS: Can I call a mixin as an argument when calling another mixin?

As discussed in comments, Less mixins are not functions and the mixin calls cannot return any value. Because of this, one mixin (or its output value) cannot be passed as an argument to another mixin.

Having said that, we can still set a variable within a mixin, call the mixin within each selector block where it is required and make use of the variable defined within it. The mixin call effectively exposes the variable defined within it to the parent scope.

Below is a sample snippet which would call the contrast mixin and assign the calculated value as the text color and border color of the element.

// color variables for user's color
@userColor: #13acae;
@darkUser: hsl(hue(@userColor), saturation(@userColor), lightness(tint(@userColor, 30%)));
@lightUser: hsl(hue(@userColor), saturation(@userColor), lightness(shade(@userColor, 30%)));

// color mixin to alter user's color using Less 'darken' and 'contrast' functions
.contrastColorDark(@percent) {
@color: darken(contrast(@userColor, @darkUser, @lightUser), @percent);
//color: darken(contrast(@userColor, @darkUser, @lightUser), @percent);
}

// border mixin
.border(@width, @color) {
border: @width solid @color;
}

// CSS rule using both mixins
.thing {
.contrastColorDark(10%);
color: @color;
.border(1px, @color);
}

.thing2 {
.contrastColorDark(50%);
color: @color;
.border(1px, @color);
}

LESS: Mixin with a unique argument to manage css3 transitions

You can use string escaping and interpolation like this:

.links-transition (@arg:"color 1s, border-color 1s") {
@color-time: ~"@{arg}";
-webkit-transition:@color-time;
-moz-transition:@color-time;
-ms-transition:@color-time;
-o-transition:@color-time;
transition:@color-time;
}

a {
color:red;
.links-transition ("color 2s, border-color 2s");
}

it will return this CSS:

a {
color: red;
-webkit-transition: color 2s, border-color 2s;
-moz-transition: color 2s, border-color 2s;
-ms-transition: color 2s, border-color 2s;
-o-transition: color 2s, border-color 2s;
transition: color 2s, border-color 2s;
}

hope this does what you want.

For more ideas: there are some additinal approaches/solutions that you can find on SO, like this two for example:

  • Less CSS: Mixins with Variable Number of Arguments

  • Multiple properties are getting treated as separate arguments in mixins


Update: in LESS 1.4 beta it works the way you wanted to do it:

.links-transition (@color-time: color 1s, border-color 1s;) {
-webkit-transition:@color-time;
-moz-transition:@color-time;
-ms-transition:@color-time;
-o-transition:@color-time;
transition:@color-time;
}

a {
color:red;
.links-transition (color 2s, border-color 2s;);
}

has the same output as the above solution. Comma separated argunets are possible since 1.3.2, however they can apparently not include whitespaces.

Is it possible with LESS CSS to store multiple arguments in a variable?

In short: no, currently this is not possible (a variable always goes as one mixin argument). (The "list to arguments expansion" feature is actually implemented and may appear in Less 2.0 but it needs some upvotes, see #1857 - do not hesistate to +1 there).

Workarounds:

1

You can create 'smart arguments handling' via mixin specializations (see Pattern Matching).
E.g. (Less 1.6+):

.border-radius(@tr: 0, @br: 0, @bl: 0, @tl: 0) when (default()) {
border-top-right-radius: @tr;
border-bottom-right-radius: @br;
border-bottom-left-radius: @bl;
border-top-left-radius: @tl;
}

.border-radius(@values) when (length(@values) = 4) {
.border-radius(
extract(@values, 1),
extract(@values, 2),
extract(@values, 3),
extract(@values, 4));
}

// you also have to provide specializations for
// (length(@values) = 2) and (length(@values) = 3)
// cases if you need to handle them

// ...............................................
// usage:

@myRadius: 3px, 0, 0, 3px;

a {
.border-radius(1px, 2px);
}

b {
.border-radius(@myRadius);
}

2

Same as above, just a bit less verbose variant (Less 1.5+):

.border-radius(@values...) {
.-() {@tr: 0; @br: 0; @bl: 0; @tl: 0} .-();
.-() when (length(@values) > 0) {@tr: extract(@values, 1)}
.-() when (length(@values) > 1) {@br: extract(@values, 2)}
.-() when (length(@values) > 2) {@bl: extract(@values, 3)}
.-() when (length(@values) > 3) {@tl: extract(@values, 4)}

border-top-right-radius: @tr;
border-bottom-right-radius: @br;
border-bottom-left-radius: @bl;
border-top-left-radius: @tl;
}

// ...............................................
// usage:

@myRadius: 3px, 0, 0, 3px;

a {.border-radius(1)}
b {.border-radius(1, 2)}
c {.border-radius(1, 2, 3)}
d {.border-radius(1, 2, 3, 4)}
e {.border-radius(1 2 3 4)}
f {.border-radius(@myRadius)}
// etc.

3

Not a workaround but "just in case" remark, if the use case is limited to basic CSS properties it's actually questionable if you really need default values to be 0 instead of actual CSS "default value" (i.e. why would we need .border-radius(1, 2) to be expanded to border-radius: 1 2 0 0; and not to border-radius: 1 2;?). Contrary I would expect the mixing to follow CSS syntax and that way it can be as simple as:

.border-radius(@values...) {
// ...
border-radius: @values;
}

// ...............................................
// usage:

@myRadius: 3px 0 0 3px; // no commas here

a {.border-radius(1)}
b {.border-radius(1, 2)}
c {.border-radius(1, 2, 3)}
d {.border-radius(1, 2, 3, 4)}
e {.border-radius(1 2 3 4)}
f {.border-radius(@myRadius)}

P.S. And as my always: if your use cases for this kind of stuff are limited to vendor prefixes, consider to add an autoprefixing tool into your build chain to stop wasting your time for writing these vendorizing preprocessor mixins (see also).

Less CSS, please explain Advanced arguments and the @rest variable to me?

Well, okay you should also read http://lesscss.org/features/#mixins-parametric-feature-pattern-matching.

In Less only mixin that match the number of arguments of the caller are compiled. Notice also that when two or more mixins match, all of them are compiled into CSS.

When you mixin got one argument, like that shown below:

.mixin(@a) {}

Only callers with one argument match and will be compiled: .mixin(3); or .mixin(1) and so on. But NOT .mixin() or .mixin(1,2,3)

When you set a default value for the first argument, for instance 3, as shown below:

.mixin(@a: 3) {}

Now not only calls with 1 argument match, but also calls with zero arguments:

.mixin(@a: 3) {property: @a;}
p{ .mixin();}

outputs:

p {
property: 3;
}

Now take a look to the special ... argument, that argument matches any number of arguments. So .mixin(...) will match and get compiled the following callers .mixin(), .mixin(1) and .mixin(1,2,3,4).

When you prepend a name (including the @) to the ... argument the values will be assigned to a variable with that name:

.mixin(@listofvariables...) {
p: @listofvariables;
}
p {
.mixin(one; two; three);
}

outputs:

p {
p: one two three;
}

Notice that ... assigns the arguments to a list, which can be manipulated with the list functions too.

An mixin such as .mixin(@a; ...) is a variant of the preceding two use cases. This mixins requires a first argument set, followed by zero or any other arguments.

@arguments is a special variable that contains a list of all argument of the mixin:

.mixin(@a; @b) {p1: @arguments; p2:extract(@arguments,2); p3:@b;}
p {.mixin(1; 2);}

outputs:

p {
p1: 1 2;
p2: 2;
p3: 2;
}

So the @arguments variable can be used in any mixin and does not require an ... argument.

What would a caller for a mixin like this look like? .mixin(@a; ...)
could it be something like this: .mixin(@a,53px); ? How does it
determine where the 53px goes to?

The 53px is not assigned to a variable, but it is the second item of the @arguments list. You can get it by extract(@arguments,2).

An use case for the .mixin(@a; ...) {} can be to assign a property always when .mixin() regardless the number of arguments, example:

.mixin(@a; ...) { color: @a;}
.mixin(@a) { background-color: contrast(@a); width:100%;}
.mixin(@a; @b;) { background-color: contrast(@a); width:@b;}
div {
.mixin(red);
}
div.small {
.mixin(red,50%);
}

outputs:

div {
color: red;
background-color: #ffffff;
width: 100%;
}
div.small {
color: red;
background-color: #ffffff;
width: 50%;
}

notice that the .mixin(@a; @rest...) {} assigns 35px the first item of the @rest list. And so the following Less code:

.mixin(@color,@padding...) { 
color: @color;
padding: @padding
}
div {
.mixin(red; 10px; 20px; 5px; 5px);
}

outputs:

div {
color: red;
padding: 10px 20px 5px 5px;
}

How to define Sass mixins with variable arguments

You can set your second parameter to be false as the default value, and do a @if statement inside your mixin.

@mixin myMixin( $param1, $param2:false ){
@if $param2 == false{
// Do something
}
@else{
// Do something with both parameters
}
}

Edit: this a custom function I made to transform px unit into em unit. You can pass a list or a single value as parameter. You can also set a base and a line-height. Maybe you can find few tricks inside this function.

You can use it like this: emify(10px 16px, 16px).

This will output: 0.625em 1em

/**
*
* Emify
* Transform px unit into em
* defaults:
* base: 16
* usage:
* emify(unit)
* emify(unit, base)
* emify(unit, line-height, base)
* examples:
* emify(16px) = 1em
* emify(16px, 10px) = 1.600em
* emify(16px, 10px, 10px) = 1.600em / 1em
*
**/
@function emify( $target, $lineheight: null, $base: null ) {

@if $base == null and $lineheight == null { $base: 16 }
@if $base == null and $lineheight != null { $base: $lineheight }

[...]

}

creating mixins with arguments in LESS

Yes, it is possible to send parameters/arguments to a mixin and use it to generate the rules. In official terms, such mixins are called as parameteric mixins.

Note that parameteric mixins would not produce any output unless they are invoked from within any specific selector block. That is, without the below line, the mixin's code will never appear in the output CSS. Parameters to the function should always be passed within circular/round braces (()).

.CircleInfoBox(#FFF, #000);

Less:

.bg-primary { // LIGHTBLUE
background-color: @fashionLightBlue;
color: @fashionDarkBlue;
.CircleInfoBox(#FFF, #000); // mixin call
}

.CircleInfoBox (@color1, @background-color) { // the mixin
.serial {
color: @color1;
}
.circle {
background-color: @background-color;
}
}

Extra: Curly braces (@{color1}) is used only to form the value of a selector dynamically (through selector interpolation) or when you want to use the variable value within a String. For example, if you want to use a loop then the below code would form the selector dynamically.

@count: 1;
.serial-@{count}{
color: @color1;
}

would result in

.serial-1{
color: #ffffff;
}


Related Topics



Leave a reply



Submit