Concatenate variable with string to form another variable - css

#color-purple: "#ffffff"
#colors: purple, light-purple, green, light-green, red, light-red, grey, light-grey, lightest-grey;
.ColorsMixin(#i:0) when(#i =< length(#colors)){ //loop over icons array
#color: extract(#colors, #i); //extract the icon at current index #i
.color--#{color}{
background: #{color-#{color}};
&:before{
content: "#{color}";
}
&:after{
content: "\#{color-#{color}}";
}
}
.ColorsMixin(#i + 1);
}
.ColorsMixin();
So, I can get it to do what I want to do in the
content: "\#{color-#{color}}";
part. This will output
content: "#ffffff";
However, when I try to output the #color-purple variable as the background, LESS throws an error. It only seems to work if I wrap it in quotation marks, but the background property wants the hex code without the quotes around it.
What's the trick here?

background: #{color-#{color}};
is not valid Less syntax, the proper one would be:
background: ~'#{color-#{color}}';
Note however, the very idea of indirectly refering to a variable values via escaping is a durty kludge (quite wide-spread but still very dirty).
It works when you assign such value directly to CSS property, but it will fail for anything else, simply because such value is not a color anymore but an unquoted string with an unknown content...
E.g. the following code will fail:
#color-dark-purple: #321;
div {
#color: 'color-dark-purple';
background: fade(~'#{color}', 50%); // error, not a color value
}
The proper Less method of getting a variable value via its name is "variable reference", e.g.:
#color-dark-purple: #321;
div {
#color: 'color-dark-purple';
background: fade(##color, 50%); // OK, proper color value
}
Additionally, take a time to consider if the whole approach of having all these colors as distinct variables and then having a separate list of these variables names is really what you need. Normally a single list having both color names and values is not such awfully bloating and much more maintainable.

Related

Overriding CSS variable with the same name doesn't work until assigned to a different variable? [duplicate]

My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>
To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.
Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>
This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).

Escapes, Fades and Variable Colors [LESS] [duplicate]

It seems that evaluated color strings are not working with some built-in LESS functions.
I have tried using e() and ~"" and any combination of both.
I might find a workaround for my particular case, I’m just asking if this is this expected behaviour, or if there is a fault in my reasoning? Any insight appreciated.
For example here, the color is created from an evaluated string; note the 'missing' # in the hex value that gets added later :
.broken-mixin(#hexcode: '9719e1') {
#color: e("##{hexcode}");
// this works as expected
background-color: #color;
// this does work too
.very-simple-mixin(#color);
// Undefined_methodError: error evaluating function `fade`:
// Object #<Object> has no method 'toHSL'
background-color: fade(#color,30%);
// SyntaxError: error evaluating function `red`:
// Cannot read property '0' of undefined
background-color: rgba(red(#color), green(#color), blue(#color), 0.5);
}
Otherwise built-in functions work normally work with variables in mixins, for example :
.mixin-works(#myColor: #00ff00) {
// works just fine
background-color: fade(#myColor,30%);
// or this, works too
background-color: rgba(red(#myColor), green(#myColor), blue(#myColor), 0.5);
}
What am I missing ?
Quoting the LESS website's Function Reference:
fade
Set the absolute transparency of a color. Can be applied to colors whether they already have an opacity value or not.
Parameters:
color: A color object.
amount: A percentage 0-100%.
The fade function requires a color object as input to it and hence passing an evaluated string as a parameter to the function doesn't work.
It can be solved by using the built-in color function which converts a string into an equivalent color object like below:
background-color: fade(color("#{color}"),30%);
The other built-in functions also are not working for the same reason (that is, they expect a color object as an input).
red:
Extracts the red channel of a color object.
Parameters: color - a color object.

Built-in functions not working with evaluated strings, why?

It seems that evaluated color strings are not working with some built-in LESS functions.
I have tried using e() and ~"" and any combination of both.
I might find a workaround for my particular case, I’m just asking if this is this expected behaviour, or if there is a fault in my reasoning? Any insight appreciated.
For example here, the color is created from an evaluated string; note the 'missing' # in the hex value that gets added later :
.broken-mixin(#hexcode: '9719e1') {
#color: e("##{hexcode}");
// this works as expected
background-color: #color;
// this does work too
.very-simple-mixin(#color);
// Undefined_methodError: error evaluating function `fade`:
// Object #<Object> has no method 'toHSL'
background-color: fade(#color,30%);
// SyntaxError: error evaluating function `red`:
// Cannot read property '0' of undefined
background-color: rgba(red(#color), green(#color), blue(#color), 0.5);
}
Otherwise built-in functions work normally work with variables in mixins, for example :
.mixin-works(#myColor: #00ff00) {
// works just fine
background-color: fade(#myColor,30%);
// or this, works too
background-color: rgba(red(#myColor), green(#myColor), blue(#myColor), 0.5);
}
What am I missing ?
Quoting the LESS website's Function Reference:
fade
Set the absolute transparency of a color. Can be applied to colors whether they already have an opacity value or not.
Parameters:
color: A color object.
amount: A percentage 0-100%.
The fade function requires a color object as input to it and hence passing an evaluated string as a parameter to the function doesn't work.
It can be solved by using the built-in color function which converts a string into an equivalent color object like below:
background-color: fade(color("#{color}"),30%);
The other built-in functions also are not working for the same reason (that is, they expect a color object as an input).
red:
Extracts the red channel of a color object.
Parameters: color - a color object.

SASS for loop updating hsla lightness returns error $lightness: "96.77419" is not a number for `hsla'

I'm trying to loop a set amount of times gradually decreasing the lightness value of hsla but when I run the loop I get an error $lightness: "96.77419" is not a number forhsla'`. Can anyone advise me where I'm going wrong with this or how it can be improved?
Code
$iterations: 31;
$base: 100;
$math: $base / $iterations;
li {
background: #919190;
height: 40px;
line-height: 40px;
color: #191919;
text-align: center;
}
#for $i from 1 through $iterations {
.options:nth-of-type(#{$i}) {
background: hsla(60, 1, #{($base - $math)}, 1);
}
Codepen http://codepen.io/styler/pen/BHwjc
Sassmeister http://sassmeister.com/gist/e99733697e1b38b794fa
What I really want to do is be able to gradually increase the colour to make a shade palette, really want to be able to use this multiple times with multiple different amounts etc so it would be great if you could give me some additional advice to make this.
Sass gave you the answer: you're using strings when you shouldn't be (note the quotations in the error, that's a sure sign of a string). Interpolation gives you a string all the time no matter what. Because hsla() expects all arguments to be numbers, passing it a string results in getting the string hsla() instead of the Sass color representation for hsla(), and the lighten() function can only accept colors.
So just stop giving it a string:
.foo {
background: hsla(60, 1, ($base - $math), 1);
}

How to use variable variables in LESS with colors stored in named variables in loops and mixins?

I have a LESS loop in which I determine color values to use in CSS rules.
I get them through some quite complex vars evaluation, which forces me to use strings (If I remove the " I get a parse error).
So what I get is a variable containing a color value in form of string.
#color: "#{col_#{animal}}"
// this is in a loop, and #animal contains the name of a var ('dog', 'cat', ...)
// #col_dog, #col_cat contain a color
// #col_dog: #F9E2A0
// #col_cat: #094DD0
so if I try to assign this #color variable to a rule
.border { border-color: #color }
in CSS I get
.border {border-color: "#F9E2A0"}
Which obviously is ignored.
Is there a way to get rid of the "string" form, or a way to do the vars evaluation I need without using strings?
Thanks!
It's easy just use ##
I've been struggling with this myself for some time now. The solution is simple. Just use ## instead of # for the color. The color will then get parsed properly, and become an color object. For this to work I store the variable name 'color_cat' in a variable called #color first. The I use the variable variables technique ## to resolve the variable.
In your case this code works:
#color_dog: red;
#color_cat: yellow;
.animal-border(#animal){
#color: "color_#{animal}";
.#{animal}.border{
border-color: ##color;
}
}
.animal-border(dog);
.animal-border(cat);
Results:
.dog.border {
border-color: #ff0000;
}
.cat.border {
border-color: #ffff00;
}
Some errors associated with this problem. This one occurs when using the darken or lighten methods:
error evaluating function darken: Object # has no method 'toHSL'
Or this occurs when trying to supply the string value "#FF0000" to the color method:
error evaluating function color: argument must be a color keyword or 3/6 digit hex e.g. #FFF
Some related posts on SO:
Define variable name with variable in LESS operation
less undefined method error
Lighten color from parent in Less
Defining Variable Variables using LESS CSS
According to the docs http://lesscss.org/functions/#misc-functions-color
Parses a color, so a string representing a color becomes a color.
This should be doing what you want:
.border { border-color: color(#color) }

Resources