Scoping sass variable - css

Is there a way to add scope to sass variables?
I want to be able to attach a class to my body element. The class will refer to a set of colours that the rest of the stylesheets can access.
I have tried:
#mixin theme_one{
$color: #000;
}
.theme_one{
#include theme_one;
}
and
.theme_one{
$color: #000;
}

I've just come across the same issue myself. I wanted to have different colour themes for different sections of my site.
Using a mixin seems like the best way to go. It's nicely DRY, and easy to use. The trick is not setting your colours in your main styles blocks, but rather using only the mixin for this.
I've set up the theme colours as variables at the top so they can be edited nicely, and I've set them as lists so that multiple values can be passed without hordes of variable being defined.
So:
// Variable Definitions
$defaultColor: black white grey;
$color2: blue green brown;
$color3: red white blue;
#mixin colorSet($color: $defaultColor) {
$link: nth($color, 1);
$border: nth($color, 2);
$background: nth($color, 3);
border-color: $border;
background-color: $background;
.column {
border-color: lighten($border, 10%);
}
a {
color: $link;
&:hover {
color: darken($link, 15%);
}
}
}
// Default colours
body {
#include colorSet();
}
// Scoped colours
.my-theme-3 {
#include colorSet($color3);
}
.my-theme-2 {
#include colorSet($color2);
}
Will produce something like this:
body {
border-color: white;
background-color: grey; }
body .column {
border-color: white; }
body a {
color: black; }
body a:hover {
color: black; }
.my-theme-3 {
border-color: white;
background-color: blue; }
.my-theme-3 .column {
border-color: white; }
.my-theme-3 a {
color: red; }
.my-theme-3 a:hover {
color: #b30000; }
.my-theme-2 {
border-color: green;
background-color: brown; }
.my-theme-2 .column {
border-color: #00b300; }
.my-theme-2 a {
color: blue; }
.my-theme-2 a:hover {
color: #0000b3; }
Edit: Updated to use default mixin values.

In your case no need to use mixin, If you have set of many styles then use mixin,
ie. if you have
#mixin theme_one{
$color: #000;
height: 50px;
}
then use Mixin
otherwise for single property use only variable
$color: #fff;
.some_class01{
color: $color;
background: $color;
}
.some_class22{
border-color: $color;
}
IMP: Variable should assign at the top of your code, it means don't use it after/below where you assigned it :)

Not sure if this is what you are looking for. It looks like you may have tried something similar to this,
which should probably work. (it may just be a matter of using !default)
Your body tag with a class on it..
<body class="theme_one">
</body>
Sass variables defined in stylesheet..
//THEME ONE VARIABLES
.theme_one{
$borderColor:#333 !default;
$fontColor:#999 !default;
}
//THEME TWO VARIABLES
.theme_two{
$borderColor:#CCC !default;
$fontColor:#000 !default;
}
Pre-existing CSS which will be overwritten depending on which class is used on the body tag.
h1.someheader {
color:$fontColor;
border-bottom:1px solid;
border-color:$borderColor;
}
Otherwise you could maybe try something like this. It looks like you may have tried something similar, however there seems to be an error with your mixin ... see note below.
//mixin used to set variables for properties
#mixin themeOne($fontColor,$borderColor) {
color:$fontColor;
border-color:$borderColor;
}
#include themeOne(#000,#CCC);
Pre-existing CSS
h1.someheader {
color:$fontColor
border-color:$borderColor;
border-bottom:1px solid;
}
Also note in your mixin example you are using $color:#000; ... This won't be interpreited properly as it should be color:#000; You can't use variables as selectors
unless you do something like #{$color}:#000;
I haven't quite tested this yet, so some things might need to be adjusted. If this doesn't solve your problem I hope it at least gives you some ideas.

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. */

Getting the default state color (currentColor) in a different state such as hovered

Is there a keyword like currentcolor which allows us to get the color of a class in its default state?
For example, I'm trying to create a re-useable button style, and currentcolor keyword helps a lot until I try to create the :hovered state.
.outline-btn {
background-color: transparent;
border: 1px solid currentColor;
padding: 0.5em 1.5em;
}
.rounded-btn {
border-radius: 50px;
}
The default state looks the way we want and changing the color or the font-size would also adjust the rest of the properties.
But we want the :hovered state to invert the colors (white text and orange background in this case)
.outline-btn:hover, .outline-btn:active, .outline-btn:focus {
background-color: currentcolor;
color: white;
}
But since in this state the color becomes white, everything else also turns white.
Is there a way that we can achieve the behavior that we want without having to create multiple classes for the different button styles that we want?
Desired effect on hover:
Also I forgot to mention that I am using SCSS if that helps.
Thanks for your time :)
If you think about it, you're essentially wanting currentColor to act as a variable -- to hold a constant value. The upcoming CSS variables will help with this, but until they're better supported, we have Sass variables.
By defining the colors as variables you can write them out very verbosely and specifically, but only have to change the color in one place when needed.
$btn-color: red;
$btn-bg: transparent;
.outline-btn {
background-color: $btn-bg;
border: 1px solid $btn-color;
padding: 0.5em 1.5em;
color: $btn-outline-color;
&:hover,
&:active,
&:focus {
background-color: $btn-outline-color;
color: $btn-outline-bg;
}
}
You could go a step further and have those variables set to equal previously set variables you're using for the body/html color background, e.g., $bg-bg: $body-bg; $btn-color: $text-color;. I love currentColor as well and this isn't as clean as that, but it might be more appropriate in this case.
You can then build this out as a mixin as user6292372 noted. Something like:
#mixin buttonBuilder($color, $bg) {
background-color: $bg;
border: 1px solid $color;
color: $color;
&:hover {
background-color: $color;
color: $bg;
}
}
...
.outline-btn {
#include button-builder($btn-color, $btn-bg);
}
Then you can easily make multiple variants.
this can't be done with css only
if you use helpers like SCSS or Less you could make yourself a mixin where you only insert the color you want as a parameter.
but you would still have to make several classes (as many as you need different colors) but can reuse your mixin within like this (scss example):
#mixin invertHover($color) {
background-color: transparent;
border: 1px solid $color;
color: transparent;
&:hover {
background-color: $color;
border: 1px solid transparent;
color: $color;
}
}
.blue-box { #include invertHover('blue'); }
.black-box { #include invertHover('#000000'); }

How to create a proper class structure?

I'm using LESS as css compiler.
Everything works fine, but now I need to create a specific class structure and I'm a bit stuck.
I'd like to have this structure:
.default .{color} {.icon-after/.icon-before} {.icon}
this is the code that I've done:
.default {
&.disabled {
background: lighten(#grayBackground, 5%);
color: lighten(#darkText, 35%);
cursor: default;
border: #grayBorder;
text-shadow: #grayTextShadow;
}
&.gray {
background: #grayBackground;
color: #darkText;
border: #grayBorder;
text-shadow: #grayTextShadow;
&:hover {
background: darken(#grayBackground, 5%);
}
}
&.green {
background: #greenBackground;
border: #greenBorder;
color: #lightText;
text-shadow: #greenTextShadow;
&:hover {
background: darken(#greenBackground, 10%);
}
}
&.yellow {
background: #yellowBackground;
border: #yellowBorder;
color: #lightText;
text-shadow: #yellowTextShadow;
&:hover {
background: darken(#yellowBackground, 10%);
}
}
&.blue {
background: #blueBackground;
border: #blueBorder;
color: #lightText;
text-shadow: #blueTextShadow;
&:hover {
background: darken(#blueBackground, 10%);
}
}
&.black {
background: #blackBackground;
border: #blackBorder;
color: #lightText;
text-shadow: #blackTextShadow;
&:hover {
background: darken(#blackBackground, 10%);
}
}
&.red {
background: #redBackground;
border: #redBorder;
color: #lightText;
text-shadow: #redTextShadow;
&:hover {
background: darken(#redBackground, 10%);
}
}
&.icon-before{
.IconDefaultStyleBefore
}
&.icon-after{
.IconDefaultStyleAfter()
}
}
obviously this doesn't work, as the result is something like this:
.default .{color / .icon-after / .icon-before}
Any suggestions on how can I obtain my structure?
Thanks a lot
EDIT
I'd like to add the classes to the buttons in this order:
.default( gives the default style )
{.colours} (so that the background, the border and all colour related properties are setted)
{.icon-after or .icon-before} so that I can choose if adding the icon before or after with the proper margin
{.icon-name} (for example a questionmark or a tick etc)
so, for example, adding this classes:
.default .blue .icon-before .tick
I will have:
default blue button with the tick icon before the text
Hope is now more clear than before.
The required structure can be achieved as shown in the below example. The code can be simplified a lot by using loops (guarded mixins).
Explanation:
#colors - An array list variable which has the list of colors required for the element.
#bckground - Another array list variable which holds the required background color for each color class declared in the #colors list.
e(extract(#colors, #index)) and extract(#bckground, #index) - Extract functions are used to fetch the color name and background color value corresponding to the index of each array iteration (similar to colors[i]). e() function is used to extract the color values without the quotes.
&.#{color} - Selector interpolation to form the selector value. & is the parent selector and #{color} is the name of the color from the #colors list variable.
length(#colors) - The no. of color items present in the #colors array list variable. This is passed to the loop function to tell the Less Compiler as to how many times the loop should be executed.
#colors: "red","green","black","blue","gray";
#bckground: #AAA, #0F0, #00F, #000, #F00;
.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));
#bgColor: extract(#bckground, #index);
&.#{color}{
background: #bgColor; //set background
/* all other props */
&:hover {
background: darken(#bgColor, 5%);
}
&.icon-before{
.IconDefaultStyleBefore;
}
&.icon-after{
.IconDefaultStyleAfter();
}
}
}
.default{
.loop-colors(length(#colors));
}
Note: As seven-phases-max mentioned in his comment, we are essentially generating a selector structure like .default.red.icon-before. Such a selector would essentially mean the same element has all the three classes and so even if it is specified like .default.icon-before.red it wouldn't make any difference but I assume that you are trying to make a more readable structure (like a default red button with an icon-before).
.default{
[...]
&.gray, &.black, [...every color...] {
.icon-before{
[...]
}
}
}
EDIT: or if you need a different .icon-before for every color you have to insert it one by one:
.default{
[...]
&.gray{
[...]
.icon-before{
[...]
}
}
}

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.

SASS CSS: Target Parent Class from Child

I am using SASS and found an inconvenience. This is an example of what I am trying to do:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
Expected CSS:
.message-error {
background-color: red;
}
p.message-error {
background-color: yellow ;
}
The idea: all elements with .message-error will be red, except if it is p.message-error. This is not real-life situation, just to show an example.
SASS is not able to compile this, I even tried string concatenation. Is there some plugin that will do exactly the same?
NOTE:
I know I can put another CSS definition like:
p.message-error{....}
...under, but I would like to avoid that and use one place for all .message-error definitions.
Thanks.
As of Sass 3.4, this is now supported. The syntax looks like this:
.message-error {
background-color: red;
#at-root p#{&} {
background-color: yellow
}
}
Note the #at-root directive and the interpolation syntax on the ampersand. Failure to include the #at-root directive will result in a selector like .message-error p.message-error rather than p.message-error.
You can assign the current selector to a variable and then use it at any depth:
.Parent {
$p: &;
&-Child {
#{$p}:focus & {
border: 1px solid red;
}
#{$p}--disabled & {
background-color: grey;
}
}
}
Natalie Weizenbaum (the lead designer and developer of Sass) says it will never be supported:
Currently, & is syntactically the same as an element selector, so it
can't appear alongside one. I think this helps clarify where it can be
used; for example, foo&bar would never be a valid selector (or would
perhaps be equivalent to foo& bar or foo &bar). I don't think this use
case is strong enough to warrant changing that.
Source: #282 – Element.parent selector
To my knowledge, there is no possible workaround.
The best thing to do would be probably this (assuming you have a little more in your .message-error class than just background color.
.message-error {
background-color: red;
}
p.message-error {
#extend .message-error;
background-color: yellow
}
This approach doesn't offer that close grouping, but you can just keep them close to each other.
I had the same problem so I made a mixin for that.
#mixin tag($tag) {
$ampersand: & + '';
$selectors: simple-selectors(str-replace($ampersand, ' ', ''));
$main-selector: nth($selectors, -1);
$previous-selectors: str-replace($ampersand, $main-selector, '');
#at-root {
#{$previous-selectors}#{$tag}#{$main-selector} {
#content;
}
}
}
To make it work, you will need a string replacement function as well (from Hugo Giraudel):
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
#return $string;
}
How it works:
SCSS
.foo {
color: blue;
#include tag(p) {
color: red;
}
}
Output
.foo {
color: blue;
}
p.foo {
color: red;
}
Use case
This method works with nested selectors but not whit compound ones.
#Zeljko It is no possible to do what you want via SASS.
See Nex3 comment: https://github.com/nex3/sass/issues/286#issuecomment-7496412
The key is the space before the '&':
.message-error {
background-color: red;
p & {
background-color: yellow
}
}
instead of:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
I think if you want to keep them grouped by parent selector, you might need to add a common parent:
body {
& .message-error {background-color: red;}
& p.message-error {background-color: yellow}
}
Of course, body could be replaced with some other common parent, such as #Content or another div name that will contain all the error messages.
UPDATE (Another Idea)
If you leverage #for and lists then it seems like this should work (what I don't know for sure is if the list will allow the . (period).
#for $i from 1 to 3 {
nth(. p. ul., #{$i})message-error {
background-color: nth(red yellow cyan, #{$i}));
}
}
Should compile to something like:
.message-error {
background-color: red;}
p.message-error {
background-color: yellow;}
ul.message-error {
background-color: cyan;}
I made a mixin that solves this problem.
Github: https://github.com/imkremen/sass-parent-append
Example: https://codepen.io/imkremen/pen/RMVBvq
Usage (scss):
.ancestor {
display: inline-flex;
.grandparent {
padding: 32px;
background-color: lightgreen;
.parent {
padding: 32px;
background-color: blue;
.elem {
padding: 16px;
background-color: white;
#include parent-append(":focus", 3) {
box-shadow: inset 0 0 0 8px aqua;
}
#include parent-append(":hover") {
background-color: fuchsia;
}
#include parent-append("p", 0, true) {
background-color: green;
}
}
}
}
}
Result (css):
.ancestor {
display: inline-flex;
}
.ancestor .grandparent {
padding: 32px;
background-color: lightgreen;
}
.ancestor .grandparent .parent {
padding: 32px;
background-color: blue;
}
.ancestor .grandparent .parent .elem {
padding: 16px;
background-color: white;
}
.ancestor:focus .grandparent .parent .elem {
box-shadow: inset 0 0 0 8px aqua;
}
.ancestor .grandparent .parent:hover .elem {
background-color: fuchsia;
}
.ancestor .grandparent .parent p.elem {
background-color: green;
}
I created package/mixin with a similar solution :) (Maybe it will help U)
https://github.com/Darex1991/BEM-parent-selector
so writing:
.calendar-container--theme-second-2 {
.calendar-reservation {
#include BEM-parent-selector('&__checkout-wrapper:not(&--modifier):before') {
content: 'abc';
}
}
}
This mixin will add selector only for the last parent:
.calendar-container--theme-second-2 .calendar-reservation__checkout-wrapper:not(.calendar-reservation--modifier):before {
content: 'abc';
}
More info on the repo.
I have ran into this before as well. Bootstrap 3 handles this using a parent selector hack. I've tweaked it slightly for my own purposes...
#mixin message-error() {
$class: '.message-error';
#{$class} {
background-color: red;
}
p#{$class} {
background-color: yellow;
}
}
#include message-error();
wheresrhys uses a similar approach above, but with some sass errors. The code above allows you to manage it as one block and collapse it in your editor. Nesting the variable also makes it local so you can reuse $class for all instances where you need to apply this hack. See below for a working sample...
http://sassmeister.com/gist/318dce458a9eb3991b13
I use an #mixin function like this, when i need change some element in middle
of a sass big tree.
The first parameters is the parent element, the target, and the second the class that should have.
SASS
#mixin parentClass($parentTarget, $aditionalCLass) {
#at-root #{selector-replace(&, $parentTarget, $parentTarget + $aditionalCLass)} {
#content;
}
}
Sample,
like i need to improve font size in a strong tag, when .txt-target had .txt-strong too
HTML
<section class="sample">
<h1 class="txt-target txt-bold">Sample<strong>Bold</strong>Text</h1>
</section>
SASS
section{
.txt-target{
strong{
#include parentClass('.txt-target','.txt-bold'){
font-weight:bold;
font-size:30px;
}
}
}
}
Font:
https://sass-lang.com/documentation/at-rules/at-root
Here you can see a function called #mixin unify-parent($child) that looks like this
This cheat might work
{
$and: .message-error;
#{$and} {
background-color: red;
}
p#{$and} {
background-color: yellow
}
}
You may even be able to use $& as your variable name but I'm not 100% sure it won't throw an error.
And SASS has inbuilt scoping, which removes having to worry about the value of $and leaking out to the rest of your stylesheet
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.
In the Current Release: Selective Steve (3.4.14) this is now possible, just need to update a little bit your code:
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
this only works if you are one level nested, for instance it does not work if you have something like this:
.messages{
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
}

Resources