Sass map produce variable names [duplicate] - css

This question already has answers here:
Creating or referencing variables dynamically in Sass
(7 answers)
Closed 7 years ago.
I have a map in Sass with keys and values
$colors: (blue: #0081d2, green: #288600, orange: #e57323);
#each $color, $value in $colors {
.#{$color} {
color: $value;
}
}
This works and gives me css classnames with the appropriate values, like:
.blue{
color: #0082d1;
}
What is would like to have is a function that produces sass variables based on the keys in the map.
$blue: #0082d1;
$green: #288600;
I have been breaking my head but can't get it to work.

You could create a function to return the value by the key specified.
Example
$colors: (blue: #0081d2, green: #288600, orange: #e57323);
#function color($color) {
#return map-get($colors, $color);
}
.bacon {
color: color(blue);
}
Results in
.bacon {
color: #0081d2;
}

The whole point of a map is to have data structured in a more hierarchical way and not just as a bunch of variables. If you don't want this, define variables instead of a map in the first place.
But I'm guessing what you really want is a way to access map values. Try this:
map-get($colors, blue)

Related

Is there a way to define SASS variables in a" [data='value'] {}", or only regular CSS variables?

I want to export "$var" like variables defined as "--var" variables from a Scss file to be used in other files with some scss functions such as "darken()", but it says its not a color.
I'm coding a Vue/Vite website and implementing a theme-change button, that will write data at the HTML tag, and use it to define the theme.
<html lang="en" data-theme="dark"> ... </html>
The base.scss looks like this:
[data-theme="dark"] {
--primary: #c6c7df;
--secondary: #c95d63;
--text-light: #e2ebf5;
}
[data-theme="light"] {
--primary: #26298d;
--secondary: #c95d63;
--text-light: #2c3e50;
}
$primary: var(--primary);
$secondary: var(--secondary);
$text-light: var(--text-light);
I can use the "$var"-like variable at the end of the file, but when I use it inside a "darken($primary, 10%)" function, it gives the following error:
Error: $color: var(--primary) is not a color.
I know it's because I defined it as a CSS variable but if I define the variable inside the "[theme] {}" thing, it doesn't get innitialized and throws the error "Undefined variable"
I know it's a niche question, and I could use something like this, but I wish I could just export it as a scss variable.
Thank you very much.
Of course we cannot make a CSS variable lighten or darken. Think about, What will happen to the Name of CSS variable in the output?
So we have to define all CSS variables and assign desired colors, Before using them.
Solution
For this purpose, you can automatically create the list of CSS variables by #each and create a manual function by #function for easy use. This way gives us a standard css output and the best experience in Sass coding.
// Theme defines area:
$theme-colors:(
"primary": #940fe0,
"secondary": #e49f38,
"warning": #ffe600,
"info": #165ad6
);
$darken-percent: 20%;
$lighten-percent: 20%;
// Functions area:
:root {
#each $name, $color in $theme-colors {
$lighten: lighten($color, $lighten-percent);
$darken: darken($color, $darken-percent);
--color-#{$name}: #{$color};
--color-#{$name}-light: #{$lighten};
--color-#{$name}-dark: #{$darken};
}
}
#function cl($name, $color-tone) {
#if not map-get($theme-colors, $name+''){
#error "`#{$name}` is not defined in Colors Theme";
}
#if $color-tone == d {
#return var(--color-#{$name}-dark);
}
#if $color-tone == l {
#return var(--color-#{$name}-light);
}
#else{
#return var(--color-#{$name});
}
}
// Our custom function usage:
// Use 'b' to return Base color. Ex: cl(primary,b)
// Use 'l' to return Lighten color. Ex: cl(primary,l)
// Use 'd' to return Darken color. Ex: cl(primary,d)
.test {
color: cl(primary,b);
color: cl(warning,l);
}
Compiled css result:
:root {
--color-primary: #940fe0;
--color-primary-light: #bf61f4;
--color-primary-dark: #550980;
--color-secondary: #e49f38;
--color-secondary-light: #f0ca92;
--color-secondary-dark: #a06916;
--color-warning: #ffe600;
--color-warning-light: #fff066;
--color-warning-dark: #998a00;
--color-info: #165ad6;
--color-info-light: #6395ef;
--color-info-dark: #0c337a;
}
.test {
color: var(--color-primary);
color: var(--color-warning-light);

CSS variables with fallback for older browsers

TL;DR: How can you use SCSS to have CSS variables with a fallback for older browsers.
I'm trying to make sense of this article. In my opinion, you have to already be an advanced SASS user to understand it, which I'm not. To make matters worse, it's the only article I found on the subject.
Here is what I'm trying to achieve:
My scss should be along the lines of :
body {
#include v(background-color, primary)
}
then the processed CSS should be
body{
background: yellow; /* Yellow being defined above as the primary color */
background: var(--color-primary);
}
By playing around a bit, I can already get the value of the CSS variable like so:
$colors: (
primary: yellow,
);
:root {
#each $name, $color in $colors {
--color-#{$name}: $color;
}
}
#mixin background-color($color_) {
background: var(--color-#{$color_});
}
To use it:
body{
#include background-color(primary);
}
Which will result in this:
body {
background: var(--color-primary);
/* But the fallback is missing :(, I tried things with the map-get but it's really eluding me... */
}
If you're using Sass, you can automate fallbacks through a Sass mixin. Create a map of your CSS variable names and their values, and then you can look up those values in a mixin that outputs the fallback style and the preferred one
$vars: (
primary: yellow,
);
:root {
--primary: map-get($vars, primary);
}
#mixin var($property, $varName) {
#{$property}: map-get($vars, $varName);
#{$property}: var(--#{$varName});
}
The above mixin is used like so:
body {
#include var(background-color, primary);
}
and outputs the following CSS:
:root {
--primary: yellow;
}
body {
background-color: yellow;
background-color: var(--primary);
}
Et voilĂ  :)
Update: Postcss Custom properties can do fallback and is way easier than the below code
step 1: declare scss variables
So first of all we want to put some variables in a $map, I'll go with color variables:
$colors: (
primary: #FFBB00,
secondary: #0969A2
);
step 2: automate css 4 var generation
// ripped CSS4 vars out of color map
:root {
// each item in color map
#each $key, $value in $colors {
--colors-#{$key}: $value;
}
}
What happens in root is : for each key and value in the colors map, we print the followng :
--colors-#{$key}: $value;
Which corresponds to css variable declarations. I believe the weird bit with #{} around the key is to not have spaces around the value.
Thus the result is:
--colors-primary: #FFBB00,
--colors-secondary: #0969A2
Note that the prefix (--colors-) is the same name as the scss color map above it. The why will become clear in last step.
step 3: Plenty of maps !
$props: (
background-color: $colors
);
$map-maps: (
background-color: colors
);
Here we add the map $props which maps a css property to the map containing the values. background-color will hold color, so the correct map is $colors.
map-maps is a copy of props where instead of the map we have the name of said map. (this is relative to the note in step 2).
Step 4 : let's make it work !
#mixin v($prop, $var) {
// get the map from map name
$map: map-get($props, $prop);
// fallback value, grab the variable's value from the map
$var-fall: map-get($map, $var);
// our css4 variable output
$var-output: var(--#{$map}-#{$var});
#{$prop}: $var-fall;
// css4 variable output
#{$prop}: $var-output;
}
body{
#include v(background-color, primary);
}
I simplified the code in the article quite a bit, it still works, for this example at least, the code in the article takes more into account.
Anyhow, here is what happens.
First, we call the mixin with:
#include v(background-color, primary);
Then upon entering,
$map: map-get($props, $prop); // map-get($props, background-color)
we have a variable called $map to which we assign the value that is inside the $props map at the key background-color which happen to be the $colors map. It's a bit of a maze but it's not that complicated once you resolve it.
Then for the fallback:
$var-fall: map-get($map, $var);
This simply gets the value of the map we just got (which is $colors) at the $var key (which happens to be primary). Thus the result is #FFBB00.
For the css var
$map-name: map-get($map-maps, $prop);
$var-output: var(--#{$map-name}-#{$var});
we recreate what we did to generate the var in the #each loop
Whole code would be :
$colors: (
primary: #FFBB00,
secondary: #0969A2
);
// ripped CSS4 vars out of color map
:root {
// each item in color map
#each $name, $color in $colors {
--colors-#{$name}: $color;
}
}
$props: (
background-color: $colors,
color: $colors
);
$map-maps: (
background-color: colors
);
#mixin v($prop, $var) {
// get the map from map name
$map: map-get($props, $prop);
// fallback value, grab the variable's value from the map
$var-fall: map-get($map, $var);
// our css4 variable output
$map-name: map-get($map-maps, $prop);
$var-output: var(--#{$map-name}-#{$var});
#{$prop}: $var-fall;
// css4 variable output
#{$prop}: $var-output;
}
body{
#include v(background-color, primary);
}
Now this is a simplification of what is done in the article. You should check it out to have code a bit more robust.
I assume you are aware of the reason why it didn't show the fallback. But since it's an answer I will explain the reasons
The current mixin block has only one background property which makes the sass compiler to generate only one property. I don't think sass can identify whether 'var' is supported in browser or not. So, we have to explicitly specify if we need the fallback.
Since you already have the map all you need is to get the value by giving the key 'primary'
#mixin background-color($color_) {
background: var(--color-#{$color_});
background: map-get($colors, primary);
}
This will add the background: yellow to the body class always. Alternatively if you want to control the addition of the fallback based on condition. You can do like this
#mixin background-color($color_, $showFall) {
background: var(--color-#{$color_});
#if $showFall {
background: map-get($colors, primary);
}
}
and call like this
body{
#include background-color(primary, true);
}
Code pen for the same
https://codepen.io/srajagop/pen/xdovON
Note: I am writing the answer under the assumption that you want only the background-color to work and not all the other properties like mentioned in that post. For that you need to create a proper data structure

Want to take a scss assoicated array and generate variables and classes from them

This is primarily a syntax question, I'm pretty sure I can do this but I don't know the best way to unquote, declare variables, and concatenate strings in the for loop. Essentially though I want to take this map I made and generate the variables for the colors as well as some classes that will assign styles such as font-color and background-color.
$color-collection: ('white': '#FFFFFF', 'black': '#000000', 'goldenrod': '#F59600');
#for $i from 0 to length($color-collection) {
//create a variable for each color name, e.g. $white: #FFFFFF;
//create a class for each variable name with a style, e.g. .bg-color-white {background-color: $white;}
}
First you need to use #each instead of #for.
Second you can just get the two variables you need in the loop itself. The first variable will be the key, and the second the value.
Third we can use sass interpolation to include the variable in a class name and to unqote the value in a selector. I think you could also use unqote($color-value) for this, or you could just write your color values in the array without quotation-marks.
Se example below.
$color-collection: ('white': '#FFFFFF', 'black': '#000000', 'goldenrod': '#F59600');
#each $color-name, $color-value in $color-collection {
.bg-color-#{$color-name} {
background-color: #{$color-value};
}
//create a variable for each color name, e.g. $white: #FFFFFF;
//create a class for each variable name with a style, e.g. .bg-color-white {background-color: $white;}
}
http://sassmeister.com/gist/e321b7e36ed0e74cd2ce
Since you are using libsass here's some code that would work with older Sass syntax:
$color-collection: ('white' '#FFFFFF'), ('black' '#000000'), ('goldenrod' '#F59600');
#each $color in $color-collection {
$color-name: nth($color, 1);
$color-value: nth($color, 2);
.bg-color-#{$color-name} {
background-color: #{$color-value};
}
}
http://sassmeister.com/gist/28caaf9d5e644d5db0cc

SASS Variable within string [duplicate]

This question already has answers here:
Can I use variables for selectors?
(4 answers)
Closed 7 years ago.
Hi all I'm new to SASS (late I know) and playing around with mixins.
Basically is there a way to link a variable to a string here is what I'm trying to do but it throws errors.
(This is a condensed version)
#mixin post-link ($class, $color, $hover) {
a.$class:link {
color: $color;
}
a.$class:hover {
color: $hover;
}
}
Link I say this is a little simpler than what I am trying to do in the mixin (more variables in full one).
EDIT: should add i'm using Compass.
Thanks
Yes, you just have to use variable interpolation. Example:
#mixin post-link ($class, $color, $hover) {
a.#{$class}:link {
color: $color;
}
a.#{$class}:hover {
color: $hover;
}
}
Example on SassMeister: http://sassmeister.com/gist/9533103
The key is adding #{ and } around your variable names to get them expanded.

Sass #each with multiple variables

I'm just getting started with Sass and Compass, and I'm loving it. Something I'd like to do is take advantage of the #each function to simplify repetitive tasks. However, I've only seen examples of #each inserting one variable, and I'd like to be able to use multiple variables.
The standard way (from the Sass Reference):
#each $animal in puma, sea-slug, egret, salamander {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
}
}
Which is great, but I'd like to be able to do something like:
#each {$animal $color} in {puma black}, {sea-slug green}, {egret brown}, {salamander red} {
.#{$animal}-icon {
background-color: #{$color};
}
}
Is this possible?
Just came across this, have the answer for you. In Sass, you can actually have a multidimensional list, so instead of constructing individual variables, you'd create one variable to hold them all, then loop over them:
$zoo: puma black, sea-slug green, egret brown, salamander red;
#each $animal in $zoo {
.#{nth($animal, 1)}-icon {
background-color: nth($animal, 2);
}
}
You can have multidimensional lists just like you would have single dimensional lists as long as each nested dimension is separated in a different manner (in our case, commas and spaces).
UPDATE Oct 24, 2013
In Sass 3.3, there is a new data type called maps which are a hashed set of items. With this, we can rewrite my previous answer in the following way to much more closely resemble the desired result:
$zoo: ("puma": black, "sea-slug": green, "egret": brown, "salamander": red);
#each $animal, $color in $zoo {
.#{$animal}-icon {
background-color: $color;
}
}
You can see this in action over at SassMeister
I'm in the same boat (beginner to Sass/Compass) and had to do something similar. Here's what I came up with, using nested lists:
$flash_types: (success #d4ffd4) (error #ffd5d1);
#each $flash_def in $flash_types {
$type: nth($flash_def, 1);
$colour: nth($flash_def, 2);
&.#{$type} {
background-color: $colour;
background-image: url(../images/#{$type}.png);
}
}
It's not the most elegant solution but it should work if you can't find anything else. Hope it helps! I'd appreciate a better method too :)
Another way I used if anyone needs it:
$i:0;
#each $name in facebook, twitter, google_plus, instagram, youtube, pinterest {
$i:$i+1;
}
This functionality is supported for Sass 3.3.0 and above (I just updated from 3.2.14 to 3.4.4 in order to use it).
#each $animal, $color in (puma, black), (sea-slug, green), (egret, brown), (salamander, red) {
.#{$animal}-icon {
background-color: $color;
}
}
I'd recommend to check the changelog for backwards incompatibilities, if you're updating Sass.
Sass reference for multiple assignments with #each
Another solution could be to create different lists and "zip" them.
//Create lists
$animals: puma, sea-slug, egret, salamander;
$animals-color: black, green, brown, red;
//Zip lists
$zoo: zip($animals, $animals-color);
//Do cycle
#each $animal, $color in $zoo {
.#{$animal}-icon {
background-color: $color;
}
}
Probably this solution is more complicated to mantain than the others, but if you use a list more than one time, you can save time. (was my case)

Resources