Use a particular Sass map depending on variable - css

I've run into an issue where it's easier for me to define two base maps that can be flicked between using a single variable (Seems easier for base, light/dark themes)
https://codepen.io/anon/pen/bLwNaW
I'm trying to set $theme-being-used, check it in a If/Else and use a particular map based on that result.
This makes it simple for me to come in and set $theme-being-used to dark and change all of the base variables.
I've tried:
#if (#{$theme-being-used} == light) {
#if ($theme-being-used == light) {
#if (#{$theme-being-used} == "light") {, etc..
Does anyone know if this can be done? It's not a big deal either way, just saves a little bit of time when throwing up templated sites.
Before I was achieving this by simply comment/uncommenting code, i.e.
$palette: (
// Light
ui: (
primary: #FFFFFF,
secondary: #EEEEEE,
alternate: #DDDDDD
),
// Dark - COMMENTED when I want to use light variables
/*ui: (
primary: #333,
secondary: #444,
alternate: #555
),*/
);
This is fine and pretty quick, but it'd just be easier to set it in one place and not have to do this.
Thanks.

Place $theme-being-used check to _palette function. This function take only one parameter — color name. And contains all color select logic inside itself.
Sassmeister demo.
$theme-being-used: light;
$palette: (
dark: (
primary: red,
secondary: orange,
tertiary: blue
),
light: (
primary: green,
secondary: black,
tertiary: yellow
)
);
#function _palette($color-name, $theme-name: $theme-being-used) {
// Select one of available themes
// Here there may be more complex logic in choosing themes
$theme: map-get($palette, #{$theme-name});
// Get the color from theme
#return map-get($theme, #{$color-name});
}
.s-o {
background: _palette(primary);
&:hover {
background: _palette(secondary);
}
}

Related

Accessing specific value from SASS nested list

I have the following sass variable:
$color-config:( "white": #FFF,
"black": #303133
);
and i would like to access the 'black' value (#303133) without make a loop. Something like that:
body
color: $color-config("black")
(i know, its completely wrong, its just to explain what i want)
You can use SASS's maps to store values inside variables:
$color-config:(
white: #FFF,
black: #303133
);
Then use map-get() to access it (see here):
content: map-get($color-config, white); # will return #FFF
In other words, don't use double quotes " around your variable name.
$color-config:(
'white': #FFF, //quotes are important
'black': #303133
);
#function colors($value) {
#return map-get($color-config, $value)
}
.white-font {
color: colors('white')
}

Sass loop inside a map for DRYer code

How can I make the following code more DRY?
I have setup bunch of variables below that are later referenced in a $colors map. Currently I have to edit the code in two places to add a new color to the system. And these variable number will grow in the future considerably.
https://codepen.io/umbriel/pen/LLvPPK
Here is the whole current code section
$red : #cc0000;
$blue : #1e8cea;
$green : #27a249;
$teal : #41bdbb;
$purple : #5c369e;
$-yellow : #ecd340;
#function color-palette($color) {
$map: (
light: scale-color($color, $lightness: 88%),
lighter: adjust-hue(scale-color($color, $lightness: 48%, $saturation: 32%), -8%),
base: $color,
darker: adjust-hue(scale-color($color, $lightness: -36%), 0%),
dark: scale-color($color, $lightness: -72%, $saturation: 100%)
);
#return $map;
}
$colors: (
red: color-palette($red),
blue: color-palette($blue),
green: color-palette($green),
teal: color-palette($teal),
purple: color-palette($purple),
yellow: color-palette($yellow)
);
// retrieve color from map ie. `color(primary, base)`
#function color($color-name, $color-variant:null) {
// color variant is optional
#if ($color-variant != null) {
// map inception
#return map-get(map-get($colors, $color-name), $color-variant);
} #else {
#return map-get(map-get($colors, $color-name), base);
}
}
I have tried looping the map inside the $colors map which didn't work.
$colorvars : (
red : #cc0000,
blue : #1e8cea,
green : #27a249,
teal : #41bdbb,
purple : #5c369e,
yellow : #ecd340
)
$colors: (
#each $key, $value in $colorsvars {
$key : color-palette($value)
}
)
DRY adding colors (and its variations):
There's an excellent article that describe how to do it better than I could ever explain. Basically you create a list of colors, then list of variations that contains what function should be applied to modify color and parameters for that function. Finally you kind of create an interface/one simple to use function for getting final color value. The important thing is that all colors and possible variations are declared in one place.
DRY generating selectors:
Another thing you might consider is the way to generate all these selectors. You could use #each directive to go through all colors and variations and generate all selectors/classes in 1 place. Something like:
#each $color in $colors {
#each $variation in $variations {
.c-#{$color}-#{$variation} {
background-color: a-nice-function-to-get-color($color, $variation);
}
}
}
However, this usually isn't such a great idea, as it makes searching selectors in your IDE way harder. If you're really going to have so many different colors, then it might be worth thinking about using this technique, though.

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

Generating dynamic color map in SASS

I'm trying to generate a rather automated system to define colours in SASS. I have a list of colours, defined with a hexidecimal value (like so: $color--deep-ocean: #123143;) and a global $colors: (); definition.
I then want to create their RGBA values dynamically, and given variable interpolation isn't an option in SASS, I've tried my hands at maps.
The idea is to feed a set-color function a name and a color to populate my $colors variable with map-merge, which I can then retrieve with get-color. Here below are my two function definitions and how I'm trying to use them:
#function color-set($name, $hex) {
$submap: (hex: $hex);
#for $o from 0 to 20 { // Going in increments of 5
$percentage-decimal: $o*0.05;
$percentage: $o*5;
$submap: map-merge($submap, ($percentage: rgba($hex, $percentage-decimal)));
}
$colors: map-merge($colors, ($name: $submap));
/* We should then have a map that looks like this:
$colors: (colorname: (
hex: #000000,
0: rgba(0,0,0,0),
5: rgba(0,0,0,.05),
// and so on…
100: rgba(0,0,0,1)
)
)
… right? */
}
#function color-get($name, $opacity: hex) {
#if $opacity != hex { // Returns RGBA value
#return map-get(map-get($colors,$name), $opacity);
}
#else { // Returns hexidecimal value
#return map-get(map-get($colors,$name), hex);
}
}
Please let me know if my functions are confusing! I'll try to comment them better.
This is how I'm trying to define a colour (part of _variables.scss):
color-set(bkgrnd, $color--deep-ocean);
And here's how I'm trying to use my colours:
body { // These colours have been defined, too …supposedly
background-color: color-get(bkgrnd, 80);
color: color-get(white);
}
Here is the error I get on _variables.scss:
Invalid CSS after "...ackground color": expected 1 selector or at-rule, was "color-set(bkgrnd, $"
So I feel like I'm missing something. I've had my head wrapped around this for four hours and I'm going mad. It's probably super simple stuff but I can't seem to figure it out. I use SASS casually, so I might not be aware of some syntax issue but if anyone has a clue about how to fix this, I'd be quite grateful.
Have a nice day and thank you for taking the time to read my post!
Regards,
Chris
PS: I compile this in Brackets with the Brackets SASS plugin, if ever that information is relevant.
After digging some more, I found out what my issue was. SASS functions must have a #return directive in order to work. As a result, I updated my color-set function, and added #return $colors; at the end of it. Then, I just needed to give some directive so the function wouldn't be called "out of the blue", so I assigned my $colors variable to it like such:
$colors: color-set(bkgrnd, $color--deep-ocean);
I think it might be a bit repetitive to reassign $colors to itself all the time, but given this only affects performance during compilation, and not on my final CSS file, I can get away with it. There might be a better way (and I'm all ears if you have an idea!) but for now this fits my needs just fine.
And like that, I am able to handle my colors using maps. Thanks to anyone who had a look at my problem, and sorry for posting this - I should have taken some time to cool down and think some more. I hope this helps someone else who is stumbling on the same problem to fix their issues!
Take care,
Chris
PS: If ever you wanted it, below is the full code.
_colors.scss
$color--deep-ocean: #123143;
$color--yellow: #ffce00;
// And so on, and so forth…
_mixins.scss
#function color-set($name, $hexval) {
#return map-merge($colors, ($name: $hexval));
}
#function color-get($name, $opacity: hex) {
#if $opacity != hex { // Returns RGBA value
$opacity-decimal: $opacity/100;
#return rgba(map-get($colors,$name), $opacity-decimal);
}
#else { // Returns hexidecimal value
#return map-get($colors, $name);
}
}
_variables.scss
$colors: color-set(bkgrnd, $color--deep-ocean);
$colors: color-set(main, $color--yellow);
// And so on, and so forth again…
Usage example:
body {
background-color: color-get(bkgrnd); // Returns #123143
color: color-get(main); // Returns #ffce00
}
button {
border: 2px solid color-get(main, 50); // Returns rgba(255, 206, 0, 0.5)
}

How to reference variables in a map, before the whole map is defined?

I’ll start with my code, as it should be easier to understand what I want to do:
#function get-color($color, $lightness) {
#return map-get(map-get($colors, $color), $lightness);
}
$colors: (
green: (
light: #A4EDE1,
mid: darken(get-color(green, light), 20%),
dark: darken(get-color(green, mid), 20%)
),
red: (
light: complement(get-color(green, light)),
mid: complement(get-color(green, mid)),
dark: complement(get-color(green, dark))
)
);
As you can see, I have created a multidimensional map, with my color values.
At the moment, I want to create the other colors, through the darken() and the complement() functions.
The problem with that is, that I need to reference variables inside of the $colors variable, before it is completely filled. This results in an error for my get-color() function, which tells me, there is no $colors variable.
I know it would be possible to alter the colors outside of my $colors map, but the benefit of doing it this way is, that I can always come back and define color values, that are not generated. This would be a huge benefit in maintaining the project.
So here my question: Is there any way to reference another variable in map, before the whole map is defined?
No. The mapping isn't defined until you reach that semicolon at the end. So you cannot reference any portion of it until then.
$base-color: #A4EDE1;
$colors: (
green: (
light: $base-color,
mid: darken($base-color, 20%),
dark: darken($base-color, 40%)
),
);
$colors: map-merge($colors, (
red: (
light: complement(get-color(green, light)),
mid: complement(get-color(green, mid)),
dark: complement(get-color(green, dark))
)));
.foo {
color: get-color(red, mid);
}
Unless you're looping over the mapping, I would recommend not using mappings to store your color variables. Rather, it would be better to simply write a function that will do the manipulation for you:
$base-color: #A4EDE1;
#function get-color($lightness, $variation: null, $color: $base-color) {
$color: if($variation, call($variation, $color), $color);
#if $lightness == medium {
#return darken($color, 20%);
} #else if $lightness == dark {
#return darken($color, 40%);
}
#return $color;
}
.foo {
color: get-color(mid);
border: 1px solid get-color(mid, complement);
}

Resources