LESS Declare variables using class names? - css

I have a button class that sets up padding etc for an element, followed by a class that defines a background colour.
.button {
padding: 0.5em 1em;
text-transform: uppercase;
color: #fff;
&.green {
background:#green; //declared previously
}
// ... more colours
}
Is it possible to declare the #green variable as the class name? This would save me having to copy/paste the &.green block for each colour I am wanting to use.
I've not been able to find anything the docs regarding this sort of selector, but something along the lines of:
&.(green|blue|red) {
background: #{$1};
}
which would generate the following:
.button.green{background:#00ff00;}
.button.blue{background:#0000ff;}
.button.red{background:#ff0000;}

You could achieve this by having a variable with the required list of colors, a loop to create the required rules and selector interpolation like shown below.
#colors: "green","blue","orange","red","yellow"; // the list of colors required
button {
padding: 0.5em 1em;
text-transform: uppercase;
.loop-colors(#index) when (#index > 0){ // loop to generate rules for each color
.loop-colors(#index - 1); // call for the next iteration
#color: e(extract(#colors, #index)); // pick the color value from the list one by one based on current index
&.#{color} {
background:#color;
}
}
.loop-colors(length(#colors));
}
Codepen Demo
Note: As mentioned in comments, LESS PHP is quite outdated and hence some of the new features offered by LESS (with respect to loops) are not supported by it. It could be overcome by doing the work-around mentioned in this answer.
You could also adopt an approach similar to the one mentioned by seven-phases-max in this answer (2nd option). That one achieves a similar effect without using loops.

Related

Creating custom unit for sass

I want to create custom css unit, that I'll be able to use in sass with node.js. Is there any guide about creating sass plugin for this? Just for example, I want to create unit "dpx", that will work as double pixel, so "width: 20dpx" will be processed to "width: 40px".
Other solution (not sass plugin), that can work with node is also acceptable.
Use a SASS function that accepts a font-size and returns the value doubled.
#function dpx($size) {
#return $size * 2;
}
div {
font-size: dpx(20px); // output: font-size: 40px;
}
As a simplified version of the current answer, you could also write the following:
$d: 2px;
div { font-size: 20*$d; }
I know this is an old question, but since I found it, other people will find it too.
In such case as yours a good solution would be to make a 1rem equal to 2px.
You can do it this way:
html {
font-size: 2px;
}
now each 1rem will be equal to 2px. If you want to make sure this doesn't break your current page, you can always add
body {
font-size: 8rem;
}
to set the global font-size to 16px (just a guess since this is a default value).

SASS Customize Class Names with Variables

Is there any way to customize the variables in SASS?
For example:
.m-b-{$number} {
margin-bottom: $number;
}
If I give class="m-b-50" to an element, it should take margin-bottom 50. I just want to know if it is possible with SASS.
Yes it is possible with the help of variable interpolation or variable substitution which uses #{} for variable substitution in SASS and mixins which is a block of code just like function.
Interpolation is the process of evaluating an expression or a string containing one or more variables, yielding a result in which the variables are replaced with their corresponding values.
Simple example of interpolation and set values to the css property in SASS:
$number:60;
$n: 20px;
.m-b-#{$number}{
margin-bottom: #{$number}px;
margin-top: $n;
}
To create customize class names, will use mixins:
#mixin margin-class($side, $number) {
$firstLetter: str-slice($side, 0, 1);
.m-#{$firstLetter}-#{$number}{
margin-#{$side}: #{$number}px;
}
}
$margins: (10, 20);
$sides: ("top", "right", "bottom", "left");
#mixin generate-margin(){
#each $margin in $margins{
#each $side in $sides{
#include margin-class($side, $margin);
}
}
}
#include generate-margin();
Here, generate-margin() will get executed which will call margin-class() for each $margins and $sides, and will generate the below CSS classes:
.m-t-10 {
margin-top: 10px;
}
.m-r-10 {
margin-right: 10px;
}
.m-b-10 {
margin-bottom: 10px;
}
.m-l-10 {
margin-left: 10px;
}
.m-t-20 {
margin-top: 20px;
}
.m-r-20 {
margin-right: 20px;
}
.m-b-20 {
margin-bottom: 20px;
}
.m-l-20 {
margin-left: 20px;
}
That's the one way when you want only for specific values, but if you want to create margin class for 0-20, you can loop thru 0 to 20 as shown below:
#mixin generate-margin(){
#for $margin from 1 through 20{
#each $side in $sides{
#include margin-class($side, $margin);
}
}
}
For anyone else facing this issue, here is how one can achieve this:-
#for $i from 1 through 10 {
.mb-#{$i} {
margin-bottom: #{$i}rem;
}
}
The answer is: no it is not possible. SASS is just a language to pre-generate CSS for you. There is no on-demand, dynamic creation of classes triggered by the contents of your HTML markup. When it comes time for the browser to render your HTML and apply your specified classes, it is still just using CSS. I.e. if you assign class="m-b-50" to an element, the class .m-b-50 must already be explicitly defined somewhere. As noted in the other answers, SASS can make it easier to generate a bunch of pre-defined classes but you must know which values you want to support up front.
Now, you could generate classes for some very large, all-inclusive range like -1000 to 1000 to effectively support all values you might ever try to use and it would seem to do what you wanted, but you would be forcing your users to download a larger CSS file with, most likely, a large percentage of it being unused CSS which is wasteful and can be inconsiderate in a world of paid & limited data plans.

Updating a Variable through a Mixin

So lets say I set the background of 10 elements on the page to #base, then a user lands on the "Foo" page which has the class on the body of the page.
How does one update the #base via a css declaration? I understand that variables are local to a function (or css declaration) but there must be a method to do this! (would make styling alternative pages so easy!)
#base: #00000;
body.foo{
#base = #FFF;
}
LESS is a Preprocessor so...
...it all has to be precompiled into CSS ahead of time. That means all possible class combinations need to be made into valid CSS ahead of time. If you wanted something like this, you would need to do something like the following in your LESS:
LESS
#base: #000000;
.setColorOptions(#className: ~'', #base: #base) {
#classDot: escape(`('#{className}' == '' ? '' : '.')`);
#class: escape(#className);
body#{classDot}#{class} {
someElement {color: #base;}
.someClass {color: #base;}
// etc.
}
}
.setColorOptions();
.setColorOptions(foo, #fff);
.setColorOptions(bar, #ccc);
CSS Output
body someElement {
color: #000000;
}
body .someClass {
color: #000000;
}
body.foo someElement {
color: #ffffff;
}
body.foo .someClass {
color: #ffffff;
}
body.bar someElement {
color: #cccccc;
}
body.bar .someClass {
color: #cccccc;
}
Obviously if there were many elements and a lot of color dependent things going on, this could get big fast. Imagine 100 elements under body with three color variations as above, and you have 300+ lines of CSS, 200+ (two-thirds) of which do not apply to any one page. And this doesn't account for other changes, like background colors, etc. In such a case, it is best to set up different LESS files that import a different set of values for #base and build different style sheets to be loaded on the pages that need it. However, if you are just doing a small subset of color changes to a page, this could be a valid way to go.
There is no way to do that.
LESS has no way to know whether the selector body.foo will apply at compile time.

Creating variable groups in Sass

On the site I'm working on we were using Scaffold, which is a PHP-based system similar to Sass. It also can process Sass functions\files. Unfortunately that system is now abandonware, and we are looking on a way to move completely to Sass. There is one big feature with Scaffold though that I'm not finding a way to port to Sass, the variable groups.
Variable in Scaffold can be organized in groups and used with a point-separated markup. For example I would define them as:
#variables vargroup1{
variable1: ####;
variable2: ####;
variable3: ####;
variable4: ####;
}
And later use on the code as, for example.
body{ width: vargroup1.variable1; margin: vargroup1.variable2 + 10;}
This helps development a lot, since you can group together variables from a system and reading the CSS files you can easily know what to reference. I didn't find anything like that on the Sass documentation, anyone knows if it is possible? Or if there is anyway using Mixins to do this?
Thanks
I came across this somewhat clunky solution (see Chris Eppstein's reply) using zip and index. Apparently a maintainer of SASS added these built-in functions in response to a similar question.
To quote his reply:
$border-names: a, b, c;
$border-widths: 1px, 1px, 2px;
$border-styles: solid, dashed, solid;
$border-colors: red, green, blue;
$borders: zip($border-widths, $border-styles, $border-colors);
#function border-for($name) {
#return nth($borders, index($border-names, $name))
}
#each $name in $border-names {
.border-#{$name} {
border: border-for($name);
}
}
Would generate:
.border-a { border: 1px solid red; }
.border-b { border: 1px dashed green; }
.border-c { border: 2px solid blue; }
The "naming your variables" comes from the list "-names" at the top; you then use the index of a desired variable name from that variable list to get the nth value from another variable lists. zip is used to mush separate lists together, so that you can retrieve the same index from all lists at the same time. Wrapping that behavior in a function makes it easier to retrieve a set.
There is no equivalent in Sass. But I can think in two workarounds:
1) Sass lists and its related list functions.
Your code could look like the following:
$variables = 40px 30px 20px 10px;
body {width: nth($variables, 1); margin: nth($variables, 2) + 10;}
It's not the same because list indexes can't be strings, so you haven't any way to name your variables.
2) Define a custom function. Look at Function Directives section in Sass reference
#function variables($variable_name) {
#if ($variable_name == 'variable1') {
#return 40px;
} #else if ($variable_name == 'variable2') {
#return 30px;
}
}
body {width: variables('variable_1'); margin: variables('variable_2') + 10;}
This way is less intuitive and uglier but you can 'name your variables'.
You could use the scss/sass map function:
#use "sass:map";
$variables: (
"variable1": ####;
"variable2": ####;
"variable3": ####;
"variable4": ####;
}
body {
width: map.get($variables, "variable1");
margin: map.get($variables, "variable2") + 10;
}
Documentation
You can use SASS lists a it's related functions on a way similar to that:
// List order: top, bottom, left, right, width, height, ...
$Header: 10px,auto,10px,auto,100%,50px;
$Footer: auto,0px,0px,auto,100%,20px;
#function getVar($variable,$name:top){
$var_index:1;
#if $name==bottom {
$var_index:2;
} #else if $name==left {
$var_index:3;
}
// Continue de if else for each property you want.
return nth($variable,$var_index);
}
That way calling something like:
getVar($Header,left)
Should return the value of the left property for the list of Header, but changing it to getVar($Footer,top) would return the value for the top property of the "Footer Group" (Footer List of Values).
That works for the time of using the values, but a the definition, you must follow the exact order and cannot leave any empty value, the nearest to an empty value that I found is #{''} what means "Empty String with no quotes", an empty value, but is added to the CSS.

LESS issues (scope and explicit node name)

Is there any way to bypass LESS scoping? It's becoming annoying. Basically, I have a .text-box which defines background, border, etc. Then, in a sub-section there's a one-off change to add a margin-top: .text-box { margin-top: 10px }. Now I can't use .text-box within that section and get my original box styles; instead, all I get is the margin-top. How can I get the definition higher in the heirarchy? I suppose I could make it a function, and call that function in both places, but being that I'm using LESS, I want to do less and KISS. In PHP, you'd get to the global namespace by using / prefix, or in C++ using :: prefix.
Additionally, it doesn't seem like any definitions with the node name work for prototyping. Meaning, I can't declare it ul.products, and then use ul.categories { ul.products }. I have to omit the node name in order to re-use it. Meaning: .categories { .products }. Is this an oversight/impossibility?
Thanks
ok so let's say you've got your mixin defined, for example:
.text-box {
background: #eee;
border: 1px solid #ccc;
color: #333;
margin-top: 5px;
}
now you want to add property or modify it in some subsection, then simply do this:
div.content {
div.sub_section {
.text-box;
margin-top: 10px; // this will override 5px defined in the mixin.
}
}
...which is putting your mixin in place, and adding some property you need to add (which will override any property from the mixin itself BUT make sure the overriding property is defined AFTER the mixin is called.
it's not ideal solution, as it creates two declarations in the output css file (there will be one from mixin followed by the one you defined in .sub_section), but otherwise I don't know a solution to this problem other than defining a parametric mixin..
--
your second issue - I think that less doesn't support scope-limited definitions on purpose... if you really need to know that certain mixin is to be used by a specific tag, I would deal with it like so:
.ul_products { ... }
.ul_categories { .ul_products; ... }
ul.categories { .ul_categories; }
you can also define a bundle and call stuff from there:
#ul {
.products { ... }
.categories { ... }
}
ul.categories { #ul > categories; }
i hope i got it right.. ?

Resources