Variable scoping in SASS - css

Suppose I'd like to define a global value for a variable, then override it for specific selectors. According to the SASS documentation this should be possible.
Variables are only available within the level of nested selectors where they’re defined. If they’re defined outside of any nested selectors, they’re available everywhere.
Logic would then state that "green" and "red" as values for $text-color would only be available within their respective nested selectors, and that $text-color would take a value of "blue" when called anywhere else, such as within .foo.
$text-color: blue;
.green {
$text-color: green;
color: $text-color;
}
.red {
$text-color: red;
color: $text-color;
}
.foo {
color: $text-color;
}
This is not the case, however, and the above SASS compiles to:
.green {
color: green;
}
.red {
color: red;
}
.foo {
color: red;
}
Any help understanding this would be, well, helpful.

This is an old question, but for the record, as of version 3.4.0, variables are now block-scoped unless marked with the !global flag.
From the changelog:
All variable assignments not at the top level of the document are
now local by default. If there's a global variable with the same
name, it won't be overwritten unless the !global flag is used.
For example, $var: value !global will assign to $var globally.
This behavior can be detected using
feature-exists(global-variable-shadowing).
This means that this scss:
$color: red;
.sel {
$color: green;
color: $color;
}
.sel2 {
color: $color;
}
Will now produce the following css:
.sel {
color: green;
}
.sel2 {
color: red;
}

This is because once you assign a variable to be global, all further assignments to that variable are done globally too. If you want to make it local after that, you could do $local-text-color: $text-color; and then color: $local-text-color;

Related

Button-Color based on className in scss

Is there a neat, simple and beautiful way to implement something like this in scss:
&.red{
--color:red;
}
&.green{
--color:green;
}
&.blue{
--color:blue;
}
&.yellow{
--color:yellow;
}
[...]
background-color:var(--color);
i was thinking something like this:
&.$color{
--color:$color
}
im really new into scss and wanted to know if this is possible in some way.
It sounds like you want to loop through a list of colors to generate new classes, which is definitely doable with proper interpolation:
.button {
$colors: red, green, blue, yellow; // create a list of colors
#each $color in $colors { // loop through each value of a list
&.#{$color} { // need interpolation to use a sass variable in a css selector
--color: #{$color}; // need interpolation for css variable assignment
color: $color; // do not need interpolation for other css properties.
}
}
}
That will output the following compound selectors, based on the $colors list.
.button.red {
--color: red;
color: red;
}
.button.green {
--color: green;
color: green;
}
.button.blue {
--color: blue;
color: blue;
}
.button.yellow {
--color: yellow;
color: yellow;
}
However, since you're already generating classes from a list, I'd suggest creating new class names rather than compound selectors, since these increase specificity. By changing &.#{$color} to &--#{$color} in the loop above, you can get the following BEM-style classes:
.button--red {
--color: red;
color: red;
}
.button--green {
--color: green;
color: green;
}
/* etc. */

Is there a way to specify a selector from a function or variable in LESS?

I have a situation where I need to specify the nth instance of a given selector AND all following siblings. So I have this:
td:nth-child(n + 2)
{
width: 125px;
}
But I do a lot of this, with different selectors and different offsets, and repeating the nth-child(n + offset) is tedious and obscures the intent of the code.
I would prefer to do something like this:
.self-and-siblings(#selector; #offset)
{
~'#{selector}:nth-child(n + #{offset})';
}
and then invoke it like this:
.self-and-siblings('td', 2)
{
width: 125px;
}
But this doesn't compile. Is it possible to accomplish what I need?
Less doesn't have any true functions that can return values. Also a mixin can output content only within selector blocks (either a selector of its own or the parent selector). In your case there is only a string print but it is not assigned to any property (or) present within any selector block.
For your case, you can write a very simple mixin like below and use it:
.nth-child-mixin(#offset, #props){
&:nth-child(n + #{offset}){
#props();
}
}
td{
.nth-child-mixin(3, {
color: red;
});
}
td{
.nth-child-mixin(5, {
color: blue;
});
}
The above nth-child-mixin takes the offset and the properties that should be assigned as inputs. The properties is a detached ruleset which is then called from within the nth-child selector block. The compiled CSS output will look as follows:
td:nth-child(n + 3) {
color: red;
}
td:nth-child(n + 5) {
color: blue;
}
A more comprehensive mixin would be the below as you can pass it the multiplier for n also.
.nth-child-mixin(#multiplier, #offset, #props){
&:nth-child(#{multiplier}n + #{offset}){
#props();
}
}
td{
.nth-child-mixin(2, 5, {
color: blue;
});
}
The compiled CSS in this case would be the following:
td:nth-child(2n + 5) {
color: blue;
}
I feel using detached rulesets make it more clearer but if you want the mixin to just output a selector, you could make the mixin definition as follows. The only drawback is you may occasionally run into scoping issues when you need more than one such block within same nesting.
.nth-child-mixin(#multiplier, #offset){
#sel: ~":nth-child(#{multiplier}n + #{offset})";
}
td{
.nth-child-mixin(1, 3);
&#{sel}{
color: red;
}
}
td{
.nth-child-mixin(2, 5);
&#{sel}{
color: blue;
}
}
For example if you want to write two nth-child selectors like below within same td nesting then it wouldn't produce the expected output due to scope, lazy-loading etc.
td{
.nth-child-mixin(1, 3);
&#{sel}{
color: red;
}
.nth-child-mixin(3, 5);
&#{sel}{
color: red;
}
}
and that would push you into using anonymous selector boxes like in below sample to solve the scoping issues (these hacks are just terrible).
td{
& {
.nth-child-mixin(1, 3);
&#{sel}{
color: red;
}
}
&{
.nth-child-mixin(3, 5);
&#{sel}{
color: red;
}
}
}

How to reference SCSS sibling?

Assuming that I have the following HTML:
<div class="navigation__item">
<span class="navigation__item__icon"></span>
</div>
I want to apply some rules to an icon, when hovering an item, which can be described with the following CSS:
.navigation__item__icon {
color: black;
}
.navigation__item:hover .navigation__item__icon {
color: white;
}
I can achieve this using the following SCSS:
.navigation__item {
&:hover {
.navigation__item__icon { <-- here
color: white;
}
}
&__icon {
color: black;
}
}
Here, is there any way to avoid writing navigation__item? Something like "parent rule \ element".
I like Sass for logical structure so that if I want to rename the whole navigation block with elements, I can simply change navigation class name in the root, and everything is renamed. This case breaks this advantage.
Update: Actually, I have found a way to do this without using {} braces. & can be repeated more than once:
.navigation__item {
&:hover &__icon {
color: white;
}
&__icon {
color: black;
}
}
It is great, but it doesn't make much sense if I have many rules and rules for &:hover itself. The question is still open - is this possible to access sibling element definition from within the {} block.
In Stylus there is a Partial reference but I don't know anything similar in SASS. One solution could be using a variable for the parent selector:
.navigation__item {
$selector: &;
&:hover {
#{$selector}__icon {
color: white;
}
}
&__icon {
color: black;
}
}
Is usefull is you change navigation__item class for another.
EDIT: I had used a wrong example, it's OK now.

Extending the "compound" class name [duplicate]

How do I extend a Less class which is dynamically formed using & combinator?
Less which generates expected output:
.hello-world {
color: red;
}
.foo {
&:extend(.hello-world);
font-size: 20px;
}
Expected CSS output:
.hello-world,
.foo {
color: red;
}
.foo {
font-size: 20px;
}
Less does not generate expected output:
.hello {
&-world {
color: red;
}
}
.foo {
&:extend(.hello-world);
font-size: 20px;
}
Unexpected CSS output:
.hello-world {
color: red;
}
.foo {
font-size: 20px;
}
Extending a dynamically formed selector (loosely using the term) like that is currently not possible in Less. There is an open feature request to support this. Till it is implemented, here are two work-around solutions to it.
Option 1:
Write the contents of .hello and .hello-world selectors into a separate Less file (say test.less), compile it to get the CSS. Create another file for the rules of .foo, import the compiled CSS as a Less file (using the (less) directive) and then extend the .hello-world as you had originally intended to.
test.less:
.hello {
&-world {
color: red;
}
}
extended-rule.less:
#import (less) "test.css";
.foo {
&:extend(.hello-world);
font-size: 20px;
}
Compiled CSS:
.hello-world,
.foo {
color: red;
}
.foo {
font-size: 20px;
}
This method would work because by the time the test.css file is imported, the selector is already formed and is no longer dynamic. The drawback is that it needs one extra file and creates dependency.
Option 2:
Write a dummy selector with rules for all properties that need to be applied to both .hello-world and .foo and extend it like:
.dummy{
color: red;
}
.hello {
&-world:extend(.dummy) {};
}
.foo:extend(.dummy){
font-size: 20px;
}
This creates one extra selector (dummy) but is not a big difference.
Note: Adding my comment as an answer so as to not leave the question unanswered and also because the work-around specified in the thread linked in comments doesn't work for me as-is.

Set a variable in Sass depending on the selector

I’ve got a website that’s using a few different ‘main’ colors. The general HTML layout stays the same, only the colors change depending on the content.
I was wondering if I could set a color variable depending on the CSS selector. This way I can theme my website with a few variables and let Sass fill in the colors.
For example:
$color-1: #444;
$color-2: #555;
$color-3: #666;
$color-4: #777;
body.class-1 {
color-default: $color-1;
color-main: $color-2;
}
body.class-2 {
color-default: $color-3;
color-main: $color-4;
}
/* content CSS */
.content {
background: $color-default;
color: $color-main;
}
I was thinking of using a mixin for this, but I was wondering if there’s a better way to do this—with a function maybe? I’m not that great with Sass, so any help would be appreciated.
I think a mixin is the answer. (As I wrote, variables won’t work.)
#mixin content($color-default, $color-main) {
background: $color-default;
color: $color-main;
}
body.class-1 {
#include content(#444, #555);
}
body.class-2 {
#include content(#666, #777);
}
That SCSS compiles to this CSS:
body.class-1 {
background: #444444;
color: #555555; }
body.class-2 {
background: #666666;
color: #777777; }
If you wanted to group the color values together in your SCSS file, you could use variables in conjunction with the mixin:
$color-1: #444;
$color-2: #555;
$color-3: #666;
$color-4: #777;
body.class-1 {
#include content($color-1, $color-2);
}
body.class-2 {
#include content($color-3, $color-4);
}
as sass documentation explain nicely (https://sass-lang.com/documentation/variables):
Sass variables are all compiled away by Sass. CSS variables are included in the CSS output.
CSS variables can have different values for different elements, but Sass variables only have one value at a time.
Sass variables are imperative, which means if you use a variable and then change its value, the earlier use will stay the same. CSS variables are declarative, which means if you change the value, it’ll affect both earlier uses and later uses.
We may take advantage of that using a combination of sass and css variables to achieve what you want:
//theme colors
$red-cosmo: #e01019;
$green-cosmo: #00c398;
$primary-color: var(--primary-color);
body{
--primary-color: #{$red-cosmo};
}
body.univers-ride{
--primary-color: #{$green-cosmo};
}
So when I call my sass variable $primary-color, it will print as my css variable "var(--primary-color)" that will expand as $green-cosmo only if my body has the "univers-ride" class else it will be $red-cosmo the default color.
If you really want to get hacky you could also define your different color schemes in a single variable like $scheme1: class1 #333 #444, where the first value is always the name, and that is followed by all the colors in that scheme.
You can then use #each:
// Define your schemes with a name and colors
$scheme1: class1 #444 #555;
$scheme2: class2 #666 #777;
$scheme3: class4 #888 #999;
// Here are your color schemes
$schemes: $scheme1 $scheme2 $scheme3;
#each $scheme in $schemes {
// Here are the rules specific to the colors in the theme
body.#{nth($scheme, 1)} .content {
background-color: nth($scheme, 2);
color: nth($scheme, 3);
}
}
This will compile to:
body.class1 .content {
background-color: #444444;
color: #555555; }
body.class2 .content {
background-color: #666666;
color: #777777; }
body.class4 .content {
background-color: #888888;
color: #999999; }
Obviously if you don't want to combine body.class1 and .content in your selectors, you could just specify a mixin content($main, $default) and call it inside the #each using nth just like in the above code, but the point is you don't have to write out a rule for each of your classes.
EDIT There are lots of interesting answers on Creating or referencing variables dynamically in Sass and Merge string and variable to a variable with SASS.
You can also create a mixing that use the ampersand parent selector. http://codepen.io/juhov/pen/gbmbWJ
#mixin color {
body.blue & {
background: blue;
}
body.yellow & {
background: yellow;
}
}
UPDATE: its 2017 and variables does works!
#mixin word_font($page) {
#font-face {
font-family: p#{$page};
src: url('../../static/fonts/ttf/#{$page}.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
.p#{$page} {
font-family: p#{$page};
}
}
// Loop and define css classes
#for $i from 1 through 604 {
#include word_font($i);
}
If you don't want to use a variable for each color, you can use one variable for all kinds of colors. In the mixin you can choose the right color with nth. For instance, if you write the index of the color as 1, then you get the first color in the color variable.
$colors: #444, #555, #666, #777;
#mixin content($color-default-num, $color-main-num) {
background: nth($colors, $color-default-num);
color: nth($colors, $color-main-num);
}
body.class-1 {
#include content(1, 2);
}
For me the definite answer to my problem was creating a map of maps and loopig through them as follows:
$pallettes: (
light-theme: (
container-color: red,
inner-color: blue,
),
dark-theme: (
container-color: black,
inner-color: gray,
),
);
#each $pallette, $content in $pallettes {
.main.#{$pallette} {
background-color: map-get($content, container-color);
.inner-div {
background-color: map-get($content, inner-color);
}
}
}
You can simply override your scss variables inside of the class wrapper:
$color1: red;
$color2: yellow;
header { background: $color1; }
.override-class {
$color1: green;
header { background: $color1; }
}
Seems to work for me.

Resources