Less CSS - Setting Variable Within Mixin

LESS setting variable inside mixin

Once a variable is declared within a namespace or a mixin, it kind of becomes scoped or accessible only within that namespace only and it's value cannot be access outside of the scope.

Reference from Less Website: Note that variables declared within a namespace will be scoped to that namespace only and will not be available outside of the scope via the same syntax that you would use to reference a mixin (#Namespace > .mixin-name). So, for example, you can't do the following: (#Namespace > @this-will-not-work).

Solution 1:
One of the options for this particular case would be to use an unnamed namespace (&) and call the mixin within it like below:

@light: #f5f5f5;
@nav-color: #0ff;
@headerbar: #333;
@dark: #222;
@light: #f5f5f5;

.theme() when (lightness(@headerbar) > 50%) {
@nav-color: @dark;
}
.theme() when (lightness(@headerbar) <= 50%) {
@nav-color: @light;
}

&{
.theme();
div#sample1{
color: @nav-color;
}

div#sample2{
background-color: @nav-color;
}
}

All the following options are courtesy of [seven-phases-max]'s comment and are added to the answer for completeness sake.

Solution 2: Removing the default value for the @nav-color variable seems to make the code in question work as-is. This should not create any issues because either one of the .theme() mixin's guard conditions would always be matched and hence the variable would always get a value assigned.

@light: #f5f5f5;
@headerbar: #333;
@dark: #222;
@light: #f5f5f5;

.theme() when (lightness(@headerbar) > 50%) {
@nav-color: @dark;
}
.theme() when (lightness(@headerbar) <= 50%) {
@nav-color: @light;
}
.theme();

div#sample1{
color: @nav-color;
}

Solution 3:
A completely different approach to solve this problem would be to use the built-in contrast() function mentioned by seven-phases-max in this answer to totally avoid the mixin and set the variable values directly based on the lightness or darkness of another variable.


Additional Information:
To further illustrate the point, the below would work fine (though it is not exactly what you are after) and would output the correct color because the value for the @nav-color is set within its scope.

.theme() when (lightness(@headerbar) > 50%) {
@nav-color: @dark;
div#sample3{
border-color: @nav-color;
}
}
.theme() when (lightness(@headerbar) <= 50%) {
@nav-color: @light;
div#sample3{
border-color: @nav-color;
}
}

Can you set a variable inside a mixin in LESS CSS?

Yes, it can be done. There was one bug that needed to be worked around.

Example LESS Code

//Set up guarded mixins
.setHeight(@h) when (ispercentage(@h)) {
@height: 1024px;
}

.setHeight(@h) when not (ispercentage(@h)) {
@height: @h;
}

//set up main height
@mainHeight: 50%;
body { height: @mainHeight;}

//call it by itself to make global
//.setHeight(@mainHeight); <-this failed (appears to be a bug)
.setHeight(50%); // <-this worked

.subsection { height: @height; /* just to show it is setting it */}

//use it for other globals
@textAreaHeight: 0.5 * @height;
@buttonHeight: 0.2 * @height;

textarea { height: @textAreaHeight}
button { height: @buttonHeight}

//override it locally
body.fixedHeight {
.setHeight(300px);
@textAreaHeight: 0.333 * @height;
height: @height;
textarea { height: @textAreaHeight}
}

Example CSS Output

body {
height: 50%;
}
.subsection {
height: 1024px; /* just to show it is setting it */
}
textarea {
height: 512px;
}
button {
height: 204.8px;
}
body.fixedHeight {
height: 300px;
}
body.fixedHeight textarea {
height: 99.9px;
}

use variables on mixin or extend in Less.js

You are trying to call a mixin using selector interpolation, which is not possible.

As for extend, Less documentation states it clearly:

Extend is NOT able to match selectors with variables. If selector contains variable, extend will ignore it.

LESS CSS - Setting variable within mixin

UPDATE I just reread your comment and understand the problem better. This should work:

.secColor (@bgc, @prop) when (lightness(@bgc) >= 50%) and (@prop = color){
color: black;
}
.secColor (@bgc, @prop) when (lightness(@bgc) >= 50%) and (@prop = background){
background-color: black;
}
.secColor (@bgc, @prop) when (lightness(@bgc) >= 50%) and (@prop = border){
border-color: black;
}
.secColor (@bgc, @prop) when (lightness(@bgc) >= 50%) and (@prop = all){
color: black;
background-color: black;
border-color: black;
}
.secColor (@bgc, @prop) when (lightness(@bgc) < 50%) and (@prop = color){
color: white
}
.secColor (@bgc, @prop) when (lightness(@bgc) < 50%) and (@prop = background){
background-color: white;
}
.secColor (@bgc, @prop) when (lightness(@bgc) < 50%) and (@prop = border){
border-color: white;
}
.secColor (@bgc, @prop) when (lightness(@bgc) < 50%) and (@prop = all){
color: white;
background-color: white;
border-color: white;
}

Then use the mixin:

.class1 {
.secColor (#fff, color) //should only set the color property for class1
}

.class2 {
.secColor (#000, all) //should set all three properties for class2
}

ADDED MORE COMPACT VERSION

.propSwitch (@prop, @clr) when (@prop = color) {
color: @clr;
}
.propSwitch (@prop, @clr) when (@prop = background) {
background-color: @clr;
}
.propSwitch (@prop, @clr) when (@prop = border) {
border-color: @clr;
}
.propSwitch (@prop, @clr) when (@prop = all) {
color: @clr;
background-color: @clr;
border-color: @clr;
}
.secColor (@bgc, @prop) when (lightness(@bgc) >= 50%) {
.propSwitch (@prop, #000);
}
.secColor (@bgc, @prop) when (lightness(@bgc) < 50%) {
.propSwitch (@prop, #fff);
}

Less mixin and variables

No, there is no need to specify the default value for the first parameter while calling the function. Instead you can just use named parameters feature to explicitly let the compiler know that the value you are passing in the mixin call is for the 2nd parameter.

.sample{
.iconFont(@font-size:14px);
}

The above Less code when compiled would produce the below output. (Note: I had set the @green as #00ff00.)

.sample {
color: #00ff00;
font-size: 14px;
}

While using the named parameter feature, even the order in which the parameters are passed does not matter. For example, the same mixin can be called as follows:

.sample2{
.iconFont(@font-size:24px, @color: #070707);
}

And it would produce the below as output.

.sample2 {
color: #070707;
font-size: 24px;
}

How to get an inner variable inside a mixin in less?

Your mistake is in assuming that & when construction is sort of if-like thing of C-like languages. But it's not, & {} is just a plain CSS ruleset (just like div {} for example) with & as its selector, and as a plain ruleset it does not expose any internal variables to outer scopes.

Only mixins expose their internals to the outer scope when invoked, so one of the methods to achieve what you need is:

@list: #000, #fff;
div {
.custom-colors(@list, 0%, 20%, true);
color: @gradient;
}

.custom-colors(@value, @light, @dark, @lightdark) when (@lightdark = true) {
@gradient: lighten(extract(@value, 1), @light),
darken(extract(@value, 2), @dark);
}
.custom-colors(@value, @light, @dark, @lightdark) when (@lightdark = false) {
@gradient: darken(extract(@value, 1), @dark),
lighten(extract(@value, 2), @light);
}

That can be further simplified to:

@list: #000, #fff;
div {
.custom-colors(@list, 0%, 20%, true);
color: @gradient;
}

.custom-colors(@value, @light, @dark, true) {
@gradient: lighten(extract(@value, 1), @light),
darken(extract(@value, 2), @dark);
}
.custom-colors(@value, @light, @dark, false) {
@gradient: darken(extract(@value, 1), @dark),
lighten(extract(@value, 2), @light);
}

Also note that darken(somecolor, somevalue) is equal to lighten(somecolor, -somevalue) and vice-versa, so the whole thing can be further optimized depending on the actual snippet.

Dynamically define a variable in LESS CSS

This Cannot Be Done

What you desire to do is not currently possible in LESS. I can think of two possible "workarounds" if you know ahead of time what variable names you want to allow to be used (in other words, not fully dynamic). Then something like one of the following could be done:

Idea #1 (Variable Variables)

.define(@var) {
@fooBar: 0;
@fooTwo: 2;
@fooYep: 4;

@fooSet: 'foo@{var}';
}

.define(Two);
.test {
.define(Bar);
prop: @@fooSet;
}
.test2 {
prop: @@fooSet;
}

Idea #2 (Parametric Mixins)

LESS

.define(@var) {
.foo() when (@var = Bar) {
@fooBar: 0;
}
.foo() when (@var = Two) {
@fooTwo: 2;
}
.foo() when (@var = Yep) {
@fooYep: 4;
}
.foo();
}

.define(Two);
.test {
.define(Bar);
prop: @fooBar;
}
.test2 {
prop: @fooTwo;
}

CSS Output (for both ideas)

.test {
prop: 0;
}
.test2 {
prop: 2;
}

Conclusion

But I'm not sure how useful either would really be, nor do I know if it could have any real application in your actual use case (since you mention the above is not the real use case). If you want a fully dynamic variable in LESS, then it cannot be done through LESS itself.

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);
}


Related Topics



Leave a reply



Submit