LESS setting variable inside mixin - less

Why is #nav-color not getting set...?
#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();

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

Related

How to use the default variable for only one of a few variables in less

Here is my mixin
.test(#color:black; #width:100px; #height:50px) {
width:#width;
height:#height;
background:#color;
}
Here is where it's called later
.mydiv {.test('use-mixin-color'; 300px; 150px);}
How can I override the size of .mydiv, while using the color defined in the mixin?
Everything I have tried overrides the mixin color.
To Use mixin in LESS, pass those parameter to override mixin default value :
Soluations :
.test(#color:black; #width:100px; #height:50px) {
width : #width;
height : #height;
background : #color;
}
.mydiv {
.test(#width : 300px; #height : 150px);
}
OUTPUT :
.mydiv {
width: 300px;
height: 150px;
background: black;
}
Helpful :)
In addition to the accepted answer. There're multiple methods (actually infinite) but if you want your mixin to be most easy for use you can provide a "specialization" for a specific argument value or number of arguments. Like this for example:
// usage:
.foo {.test(red, 1px, 2px)}
.bar {.test(3px, 4px)}
// impl.:
.test(#color, #width, #height) {
width: #width;
height: #height;
background: #color;
}
.test(#width, #height) { // <- "no color" specialization
.test(black, #width, #height);
}
Demo.
Also think twice before adding default parameter values for a mixin like:
.test(#color: black, #width: 100px, #height: 50px) { ...
People tend to overuse this feature while it's rarely really necessary (and only creates an extra code-noise) except some specific use-cases.
I.e. consider if you actually expect your mixin to be invoked as:
test;
test(blue, 4em);
// etc.
Do you?
It's usually a good idea to start without default parameter values (at least to protect the mixin against accidental misuse), i.e.:
.test(#color, #width, #height) { ...
and add them later only where and when they are necessary.

Overriding mixins in LESS

when defining a mixin multiple times in LESS, and later calling that mixin as follows
.background-color() {
background: red;
}
.background-color() {
background: yellow;
}
body {
.background-color;
}
the result will be a combined output from all the defined mixins
body {
background: red; // << output from mixin #1
background: yellow; // << output from mixin #2
}
while when you apply the same scenario in both Sass & Stylus ( using their own syntax of course ), when you call a mixin that is defined multiple times across your stylesheets, Only the last defined one will be executed ( it will override all previously defined mixins ) as follows.
result Sass and Stylus
body {
background: yellow; // << output from mixin #2
}
how can I override a mixin in LESS so that the output will be from the last defined mixin ?
You can not override them, alternatively use a variable to define the 'background-color'. For Less variables the last declared win.
Also read Pattern-matching
In Less all matching mixins are compiled in the source. You can use namespace to prevent name collisions, for instance:
#ns1 {
.background-color() {
background: red;
}
}
#ns2 {
.background-color() {
background: yellow;
}
}
than you can use:
body {
#ns2 > .background-color;
}
Double properties are also not removed to make some browser hacks possible, example:
#myElement {
width: 300px;
width: 500px\9;
}
To find a solution for your use case you should reformulate your question and explain why you have these same named mixins in the first place.

How do I make a list of CSS rules in LESS based on an unknown number of input arguments?

I'd like to make a LESS mixin for translating images like so:
.translate('/images/image.png', de, en-uk);
with an output that looks like this:
background-image: url('/images/image.png');
&:lang(de){ background-image: url('/images/image_de.png') }
&:lang(en-uk){ background-image: url('/images/image_en-uk.png') }
This would be easy if we were always translating the same number of languages, but unfortunately we are not (the content is the same across certain locales). I'm not sure how to make this number variable (which would future-proof the solution).
I guess what I'm looking for is a way to loop over each element in an array I pass and return another LESS rule for each.
Any ideas?
See Variadic mixin arguments, Loops, List Functions. For example it could be implemented somewhat like:
.test {
.translate('/images/image.png', grc, lat, san);
}
.translate(#image, #langs...) {
background-image: #image;
.-(length(#langs));
.-(#i) when (#i > 0) {
.-((#i - 1));
#lang: extract(#langs, #i);
&:lang(#{lang}) {
background-image: replace(#image, "\.", "_#{lang}.");
}
}
}
replace function requires Less 1.7.0 but for earlier versions you can use plain string interpolation/concatenation or format function as in #helderdarocha answer.
(Also note that the #langs... mixin parameter above can also accept the language list as a single variable), e.g.:
#languages: de, fr, es, ru, en-uk; // in fact commas here are optional too
.test {
.translate('/images/image.png', #languages);
}
And just in case, the same mixin using for wrapper (just to show that Less loops don't have to be that scary :):
#import "for";
.translate(#image, #langs...) {
background-image: #image;
.for(#langs); .-each(#lang) {
&:lang(#{lang}) {
background-image: replace(#image, "\.", "_#{lang}.");
}
}
}
This mixin uses target languages from a variable. It will loop through them and generate the code you want for each one:
.image-replace(#languages; #image-prefix) {
#count: length(#languages);
.loop(#count; #image-prefix);
.loop(#count; #image-prefix) when (#count > 0) {
.loop(#count - 1; #image-prefix);
#lang: extract(#languages, #count);
#image: %('%a_%a.png', #image-prefix, #lang);
&:lang(#{lang}){
background-image: url(#image);
}
}
}
To use it:
#languages: ~'de', ~'fr', ~'es', ~'ru', ~'en-UK', ~'pt-BR';
.section {
.image-replace(#languages; ~'/images/image');
}
Result:
.section:lang(de) {
background-image: url('/images/image_de.png');
}
.section:lang(fr) {
background-image: url('/images/image_fr.png');
}
.section:lang(es) {
background-image: url('/images/image_es.png');
}
.section:lang(ru) {
background-image: url('/images/image_ru.png');
}
.section:lang(en-UK) {
background-image: url('/images/image_en-UK.png');
}

Change variable used in mixin depending on scope

In the Lazy Loading section of the Less language features, it states:
When defining a variable twice, the last definition of the variable is used, searching from the current scope upwards. This is similar to css itself where the last property inside a definition is used to determine the value.
I'd like to overwrite a global variable, but this doesn't seem to work:
#border: #fff;
.table {
border: #border;
}
.table-summary {
#border: #000;
.table
}
Compiles to
.table {
border: #ffffff;
}
.table-summary {
border: #ffffff; // I want this to be #000
}
Currently the global scope has higher precedence than caller scope for a mixin (unless the mixin is defined inside parametric namespace). For more more details see #1316, some people consider this is a bug but there's no well-defined agreement on that.
Either way, the recommendation is to minimize use of non-parametric mixins and to not rely on indirect parameter passing whenever possible. Your example is a perfect use-case for a parametric mixin (even if your the code becomes slightly more verbose):
#border-default: #fff;
.table-base(#border: #border-default) {
border: #border;
}
.table {
.table-base;
}
.table-summary {
.table-base(#000);
}
Alt. if for some reason you can't modify the .table class (for example if it's defined in an external library) then just forget about any variables and override the property directly, the most optimal way would be:
#border: #fff;
.table {
border: #border;
}
.table-summary:extend(.table) {
border-color: #000;
}
---
Technically, there's method to achieve what you want with the code quite close to your original snippet but I doubt it is something to be really recommended:
#border: #fff;
.table {
border: #border;
}
.-;.-() { // dummy parametric namespace
.table-summary {
#border: #000;
.table;
}
} // end of dummy parametric namespace

Creating or referencing variables dynamically in Sass

I'm trying to use string interpolation on my variable to reference another variable:
// Set up variable and mixin
$foo-baz: 20px;
#mixin do-this($bar) {
width: $foo-#{$bar};
}
// Use mixin by passing 'baz' string as a param for use $foo-baz variable in the mixin
#include do-this('baz');
But when I do this, I get the following error:
Undefined variable: "$foo-".
Does Sass support PHP-style variable variables?
This is actually possible to do using SASS maps instead of variables. Here is a quick example:
Referencing dynamically:
$colors: (
blue: #007dc6,
blue-hover: #3da1e0
);
#mixin colorSet($colorName) {
color: map-get($colors, $colorName);
&:hover {
color: map-get($colors, #{$colorName}-hover);
}
}
a {
#include colorSet(blue);
}
Outputs as:
a { color:#007dc6 }
a:hover { color:#3da1e0 }
Creating dynamically:
#function addColorSet($colorName, $colorValue, $colorHoverValue: null) {
$colorHoverValue: if($colorHoverValue == null, darken( $colorValue, 10% ), $colorHoverValue);
$colors: map-merge($colors, (
$colorName: $colorValue,
#{$colorName}-hover: $colorHoverValue
));
#return $colors;
}
#each $color in blue, red {
#if not map-has-key($colors, $color) {
$colors: addColorSet($color, $color);
}
a {
&.#{$color} { #include colorSet($color); }
}
}
Outputs as:
a.blue { color: #007dc6; }
a.blue:hover { color: #3da1e0; }
a.red { color: red; }
a.red:hover { color: #cc0000; }
Sass does not allow variables to be created or accessed dynamically. However, you can use lists for similar behavior.
scss:
$list: 20px 30px 40px;
#mixin get-from-list($index) {
width: nth($list, $index);
}
$item-number: 2;
#smth {
#include get-from-list($item-number);
}
css generated:
#smth {
width: 30px;
}
http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#lists
http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#list-functions
Anytime I need to use a conditional value, I lean on functions. Here's a simple example.
$foo: 2em;
$bar: 1.5em;
#function foo-or-bar($value) {
#if $value == "foo" {
#return $foo;
}
#else {
#return $bar;
}
}
#mixin do-this($thing) {
width: foo-or-bar($thing);
}
Here's another option if you're working with rails, and possibly under other circumstances.
If you add .erb to the end of the file extension, Rails will process erb on the file before sending it to the SASS interpreter. This gives you a can chance to do what you want in Ruby.
For example: (File: foo.css.scss.erb)
// Set up variable and mixin
$foo-baz: 20px; // variable
<%
def do_this(bar)
"width: $foo-#{bar};"
end
%>
#target {
<%= do_this('baz') %>
}
Results in the following scss:
// Set up variable and mixin
$foo-baz: 20px; // variable
#target {
width: $foo-baz;
}
Which, of coarse, results in the following css:
#target {
width: 20px;
}
I came across the need to reference a colour dynamically recently.
I have a _colours.scss file for every project, where I define all my colours once and reference them as variables throughout.
In my _forms.scss file I wanted to setup button styles for each colour available. Usually a tedious task. This helped me to avoid having to write the same code for each different colour.
The only downside is that you have to list each colour name and value prior to writing the actual css.
// $red, $blue - variables defined in _colours.scss
$colours:
'red' $red,
'blue' $blue;
#each $name, $colour in $colours {
.button.has-#{$name}-background-color:hover {
background-color: lighten($colour, 15%);
}
}
I needed to use dynamic color values in sass variables.
After lots of search, I applied this solution:
In application.html.erb:
<style>
:root {
--primary-color: <%= current_client.header_color %>;
--body-color: <%= current_client.footer_color %>;
}
</style>
In variables.sass:
$primary: var(--primary-color);
And boom you are good to go!
Reference: https://medium.com/angular-in-depth/build-truly-dynamic-theme-with-css-variables-539516e95837
To make a dynamic variable is not possible in SASS as of now, since you will be adding/connecting another var that needs to be parsed once when you run the sass command.
As soon as the command runs, it will throw an error for Invalid CSS, since all your declared variables will follow hoisting.
Once run, you can't declare variables again on the fly
To know that I have understood this, kindly state if the following is correct:
you want to declare variables where the next part (word) is dynamic
something like
$list: 100 200 300;
#each $n in $list {
$font-$n: normal $n 12px/1 Arial;
}
// should result in something like
$font-100: normal 100 12px/1 Arial;
$font-200: normal 200 12px/1 Arial;
$font-300: normal 300 12px/1 Arial;
// So that we can use it as follows when needed
.span {
font: $font-200;
p {
font: $font-100
}
}
If this is what you want, I am afraid as of now, this is not allowed