I am trying to customize scss in my project so that I can use css variables according to css selector applied on outermost element.
The basic Idea I have is to define color variables and then use those color variables to define semantic color variables in two different css selectors.
$primary: orange;
$primary-dark: redorange;
$warn: red;
$accent: grey;
$dark-grey: #757678;
$light-grey: #f7f7f7;
$error-text: $warn;
.light {
$background: $light-grey;
$button-bg: $primary;
}
.dark {
$background: $dark-grey;
$button-bg: $primary-dark;
}
This is one solution I tried, but in scss we cannot change scope of variables according to selectors.
So I tried using functions.
$white: #fff;
$light-grey: #eaeaea
$primry-dark: redorange;
$primary: orange;
$black: #000;
$semantic-colors: (
background: (
screen: $white,
tile: $light-grey,
),
buttons: (
primary: $primary
link: $white
icons: $primary-dark;
)
);
#function semantic-color($semantic-color:'background', $tone: "base") {
// #return red;
#return map-get(map-get($semantic-colors, $semantic-color), $tone);
}
.side-nav-link {
background-color: semantic-color(background, tile);
}
The above code works fine. but I want to have a color map based on a theme. for eg:
dark has its own semantic color map and light has its own and I can access based on scope.
.light {
$semantic-colors: (
background: (
base: $white,
tile: $light-grey,
),
buttons: (
primary: $primary,
link: $white,
icons: $primary-dark;
)
);
}
.dark {
$semantic-colors: (
background: (
base: $black,
tile: $dark-grey,
),
buttons: (
primary: $primary-dark,
link: $black,
icons: $primary;
)
);
}
I can create two different maps within semantic-color map:
$semantic-colors: (
light:(
background: (
base: $white,
tile: $light-grey,
),
buttons: (
primary: $primary
link: $white
icons: $primary-dark;
)
),
dark:(
background: (
base: $black,
tile: $dark-grey,
),
buttons: (
primary: $primary-dark,
link: $black,
icons: $primary;
)
)
);
and then modify my function to get color within a specific map based on $theme variable.
For eg:
$theme: dark;
#function semantic-color($semantic-color:'background', $tone: "base") {
// #return red;
#return map-get(map-get(map-get($semantic-colors, $theme), $semantic-color), $tone);
}
but I don't know a way to define $theme according to .light and .dark selectors.
If I try doing something like this:
.light {
$theme: light;
}
.dark {
$theme: dark;
}
and then use the $theme variable in the semantic-color function then I get the error $theme is not defined.
so my question is
"Is there a way I can define $theme or semantic-color function or $sematic-color map according to a CSS selector (.light or .dark in my case)?"
I have read sass documentation but could not find any solution to suit my situation.
Articles I referred to:
Theming in SASS
Creating a Color Language in Web Interfaces (with Sass Maps)
educba SASS Map
I am not quits sure if I did it completly right ... but as I see your idea and code in general works well.
The only thing seems to be: there had been some errors in your code seeting up $semantic-colors !?
This (correctedt version = only corrections in $semantic-colors) works here:
//########### SASS: demo file
//### VARS
$primary: orange;
$primary-dark: redorange;
$warn: red;
$accent: grey;
$dark-grey: #757678;
$light-grey: #f7f7f7;
$white: #ffffff;
$black: #000000;
$error-text: $warn;
$semantic-colors: (
light: (
background: (
base: $white,
tile: $light-grey,
),
buttons: (
primary: $primary,
link: $white,
icons: $primary-dark,
),
),
dark: (
background: (
base: $black,
tile: $dark-grey,
),
buttons: (
primary: $primary-dark,
link: $black,
icons: $primary,
),
),
);
//### METHODS
$theme: dark;
#function semantic-color($semantic-color: "background", $tone: "base") {
// #return red;
#return map-get(
map-get(map-get($semantic-colors, $theme), $semantic-color),
$tone
);
}
//### STYLES
.light {
$theme: light;
background: semantic-color('background', 'base');
}
//###########################
//###### CSS: compiled file
.light {
background: #ffffff; // if $theme changes to dark this becomes #000000
}
Because of your comment I believe to understand your question better now. So, according to your last comment I add an additional answer as it is more an explanation about the general howto than a concrete answer to your code.
First: I believe your code (SASS-map/-function) works the right way
To me it seems more a question about understanding how the theme technique works.
So let's start from the base:
1. What you need to do for theming in html:
// set a theme anchor with classes
// --> class to body
<body class="theme_light">
... code ...
... all elements within body will be styled by theme-light-classes
// or ALTERNATIVE do it with a marker where your app starts in html
// --> class to app-root-element
<div class="app_root theme_light">
... code ...
... all elements within app-div will by styled by theme-light-classes
</div>
</body>
2. Based on HTML what you nee in CSS:
Write the styling for your elements for both(!) themes based on the used marker technique (body or app) ...
// write styling classes for both themes (dark + light)
// as child selector based on the html theme-anchor-classes
/* stylings light theme */
.theme_light .content {
background: #theme-light-background-color;
color: #theme-light-font-color;
}
.theme_light .button {
background: #theme-light-button-background-color;
color: #theme-light-button-font-color;
}
/* stylings dark theme */
.theme_dark .content {
background: #theme-dark-background-color;
color: #theme-dark-font-color;
}
.theme_dark .button {
background: #theme-dark-button-background-color;
color: #theme-dark-button-font-color;
}
// if you anchor with app class your css-selectors would have to be
/* light theme */
.app_root.theme_light .content { ... }
.app_root.theme_light .content { ... }
/* dark theme */
.app_root.theme_dark .content { ... }
.app_root.theme_dark .content { ... }
3. Use your SASS-function/-map to achieve this in SASS
In this step we use your (still working) function and map from your question. Writing the CSS in SASS is as easy with nesting the element classes ...
// write themes dark & white nested to the anchor classes
// use function & map from question
.theme_light {
// set sass theme varible
// to get from function values for theme light
$theme: light;
// write stylings
// function will automatic return values for light theme
body {
background: semantic-color('background', 'base');
color: semantic-color('text-color', 'base');
}
button {
background: semantic-color('buttons', 'primary');
color: semantic-color('buttons-text', 'primary');
}
}
.theme_dark {
// set sass theme varible
// to get from function values for theme dark
$theme: dark;
// write stylings
// function will automatic NOW return values for light theme
body {
background: semantic-color('background', 'base');
color: semantic-color('text-color', 'base');
}
button {
background: semantic-color('buttons', 'primary');
color: semantic-color('buttons-text', 'primary');
}
}
//###
//### --> that will compile the needed css!!!
//###
//### NOTE:
//--> if you use app-anchor-class
//--> your nesting would be as follow
.app_root.theme_light { ... same code as above ... }
.app_root.theme_dark { ... same code as above ... }
HINTS:
As you see your function is a powerful tool: as shown in the example it leads to write identical code twice. If you write a SASS mixin (use directive #content to pass individual theme styles to it) you can reduce it so you are able to write all themes at once.
As you see the SASS variable $theme is used IN SASS as switch which changes your function semantic-color() to get the right color (light or dark).
There are many approches to realize theme stylings in SASS. This is one of the traditional methods and having a good organized map a very intuitive one.
Related
Github project
Hello every one ! I'm on an angular project and i'm stuck with my scss.
I have a mixin (in its own file mixins.scss :
BUTTON MIXINS
===================== */
#mixin btn($color:false,$bgColor:false,$size:false,$hoverBgColor:false){
button {
#if $color {
color: $color;
}
#if $bgColor {
background-color: $bgColor;
}
#if $size {
font-size: $size;
}
#if $hoverBgColor {
&:hover {
background-color: $hoverBgColor;
}
}
}
}
Also i have a global scss file with my variables :
$colors: (
primary-black: #000000,
primary-white: #FFFFFF,
medium-grey: #7A746E,
light-cream: #FFF7F0,
);
/* =====================
BUTTON VARIABLE
===================== */
$btnColor:(
btnHeaderFooterColor: map-get($colors, "primary-black"),
btnBody: map-get($colors, "light-red"),
);
When i want use my mixin for my button-component nothing happen, this is an example of my code.
button-component.scss :
#use "sass:map";
#import './../../../../styles/base/mixins';
#import './../../../../styles/base/global';
button {
.btn {
padding: 0.5rem 1.7rem;
border-radius: 1.8rem;
border-color: transparent;
&__headerFooter {
#include btn(map.get($btnColor, btnHeaderFooterColor));
}
}
}
I already tried #usebut i have the same result, my code doesn't appears in the browser inspector, i applied the class in my HTML, but if you want check the project the link is available just on top of this post.
I really appreciate your help because for me it's a mystery this situation !
Thanks you for reading me. :)
I have an existing project with a scss file that uses semantic variables:
$background-color: white;
body {
background-color: $background-color;
}
I would like to change the background to black when I add a theming class to the body:
<body class="theme-dark">...</body>
and back to white if I remove the class (or switch to a theme-light).
I haven't found any light-weight methods to do this in scss (parametrizing a class for each theme seems like a very hard to maintain approach).
I've found a hybrid scss/css-custom properties solution:
original:
.theme-light {
--background-color: white;
}
update (based on Amar's answer):
:root {
--background-color: white;
}
.theme-dark {
--background-color: black;
}
$background-color: var(--background-color);
body {
background-color: $background-color;
}
Defining the scss variable as having a css-variable expansion as the value, i.e. (from above):
$background-color: var(--background-color);
generates the following css:
:root { --background-color: white; }
.theme-dark { --background-color: black; }
body { background-color: var(--background-color); }
which seems to be what we want...?
I like it since it only requires changing the definition of $background-color (not every usage in a very large scss file), but I'm unsure if this is a reasonable solution? I'm pretty new to scss, so maybe I've missed some feature..?
Doing this with SCSS is possible but you would have to add styles to all elements you want to theme. That is because SCSS is compiled at build-time and you can't toggle the variables with classes. An example would be:
$background-color-white: white;
$background-color-black: black;
body {
background-color: $background-color-white;
}
.something-else {
background-color: $background-color-white;
}
// Dark theme
body.theme-dark {
background-color: $background-color-black;
.something-else {
background-color: $background-color-black;
}
}
The best way to currently do it is by using CSS variables. You would define the default variables like this:
:root {
--background-color: white;
--text-color: black;
}
.theme-dark {
--background-color: black;
--text-color: white;
}
Then, you would use these variables in your elements like this:
body {
background-color: var(--background-color);
color: var(--text-color);
}
If the body element has the theme-dark class, it will use the variables defined for that class. Otherwise, it will use the default root variables.
All credit goes to Dmitry Borody
I would recommend an approach like what is mentioned in this Medium article. With this approach, you can assign what classes need to be themed without specifically mentioning the theme name so multiple themes can be applied at once.
First, you set up a SASS map containing your themes. The keys can be whatever makes sense to you, just make sure that each theme is using the same name for the same thing.
$themes: (
light: (
backgroundColor: #fff,
textColor: #408bbd,
buttonTextColor: #408bbd,
buttonTextTransform: none,
buttonTextHoverColor: #61b0e7,
buttonColor: #fff,
buttonBorder: 2px solid #fff,
),
dark: (
backgroundColor: #222,
textColor: #ddd,
buttonTextColor: #aaa,
buttonTextTransform: uppercase,
buttonTextHoverColor: #ddd,
buttonColor: #333,
buttonBorder: 1px solid #aaa,
),
);
Then use the mixin and function pair to add theme support.
body {
background-color: white;
#include themify {
background-color: theme( 'backgroundColor' );
}
}
.button {
background-color: lightgray;
color: black;
#include themify {
background-color: theme( 'buttonBackgrounColor' );
color: theme( 'buttonTextColor' );
}
&:focus,
&:hover {
background-color: gray;
#include themify {
background-color: theme( 'buttonBackgroundHoverColor' );
color: theme( 'buttonTextHoverColor' );
}
}
}
If you're going to be adding a lot of themes or a theme will be touching a lot of stuff, you might want to set up your SCSS files a little differently so that all the theming doesn't bloat your main CSS file (like the example above would do). One way to do this might be to create a themes.scss file and replicate any selector paths that need theming and have a second build script that outputs just the themes.scss file.
The Mixin
#mixin themify( $themes: $themes ) {
#each $theme, $map in $themes {
.theme-#{$theme} & {
$theme-map: () !global;
#each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
#content;
$theme-map: null !global;
}
}
}
The Function
#function themed( $key ) {
#return map-get( $theme-map, $key );
}
I have a scss file with over 20 lines of color variables. I want to create a css file with these variables to have one class for color and class for background-color.
Example:
from scss variable
$color-red: #FF0000;
to css
.color-red { color:#FF0000; }
.bg-color-red { background-color:#FF0000; }
Is there any Gulp plugin that allows me to do it?
any recomendation accepted but i need to do it with gulp if it is possible.
As #chriskirknielsen suggested, sass maps and the #each rule is a good choice here. This is the first time I have used these concepts but this works in my testing.
Your colors.scss file would look like this:
$colors: ("color-red": "#FF0000", "color-green": "#00ff40"); // etc.
#each $color, $value in $colors {
.#{$color} {
color: #{$value};
}
.bg-#{$color} {
background-color: #{$value};
}
}
The gulp-sass output in colors.css is:
.color-red {
color: #FF0000; }
.bg-color-red {
background-color: #FF0000; }
.color-green {
color: #00ff40; }
.bg-color-green {
background-color: #00ff40; }
See https://sass-lang.com/documentation/values/maps#do-something-for-every-pair
I am looking to utilize a SASS Rules (#mixin, #extend, etc.) and/or control directives (#if, #for, #each, etc.) to generate classes for the color variables that I have defined.
Below is an example of what I am using currently, but I know that I can further simplify this to make it significantly less redundant.
// Colors
$navy: #37455a;
$blue: #abbdd3;
$cyan: #a3d9cb;
$peach: #ff9d7a;
// Backgrounds
.bg-navy{
background: $navy;
}
.bg-blue{
background: $blue;
}
.bg-cyan{
background: $cyan;
}
.bg-peach{
background: $peach;
}
// Text
.text-navy{
color: $navy;
}
.text-blue{
color: $blue;
}
.text-cyan{
color: $cyan;
}
.text-peach{
color: $peach;
}
I have tried a few different methods, but I always run into conflicts with mapping. I want to preserve the mapped variable in the object to use outside of these functions. This is what I have come up with so far:
// Colors for use in other partials
$navy: #37455a;
$blue: #abbdd3;
$cyan: #a3d9cb;
$peach: #ff9d7a;
// Mapped colors used for generating classes (Duplicated unfortunately)
$colors: (
$navy: #37455a;
$blue: #abbdd3;
$cyan: #a3d9cb;
$peach: #ff9d7a;
)
// Background class definition
#mixin bg{
.bg-#{$color}{
background: $color;
}
}
// Text class definition
#mixin text{
.text-#{$color}{
color: $color;
}
}
// Background class generation
#each $color in $colors{
#include bg($color);
}
// Text class generation
#each $color in $colors{
#include text($color);
}
While what I have above does not work, it's closer to what I am trying do accomplish. Has anybody solved for what I am attempting to do here? Any insight would be much appreciated as well!
One mixin to do it all!
$navy: #37455a;
$blue: #abbdd3;
$cyan: #a3d9cb;
$peach: #ff9d7a;
$colors: (
navy: $navy,
blue: $blue,
cyan: $cyan,
peach: $peach
);
#mixin gen-props($prefix, $property) {
#each $color-name, $color in $colors {
.#{$prefix}-#{$color-name} {
#{$property}: $color
}
}
}
#include gen-props('text', 'color');
#include gen-props('bg', 'background');
I'm new with Sass stuff and I've been reading about different ways to use variables, this principle I'm trying to apply is just for colors, some of the solutions I've found were something like this (map-get):
$colors: (
lighestGray: #F8F8FA,
lightGray: #A5ACBA,
light: #FFFFFF,
dark: #000000,
link: #428bca,
linkHover: #555,
navBlue: #7AC243,
navGreen: #009CDC,
);
Then you use it on your class like this:
.my-class {
color: map-get($colors, dark);
}
And the other way is to use:
$color-black: #000000;
Then you use it like this:
.my-class {
color: $color-black;
}
My question is, which option is better? or map-getfunction has another purpose?, has Sass a pattern for this or it depends on each web-developer?.
Thanks for helping me out!.
Regards.
The differences is that when you use $map variables, they are best designed for using through iterations or using #each.
Sample case:
SCSS
// Map variable
$icons: (
facebook : "\f0c4",
twitter : "\f0c5",
googleplus : "\f0c6",
youtube : "\f0c7"
);
// Mixin doing the magic
#mixin icons-list($map) {
#each $icon-name, $icon in $map {
#if not map-has-key($map, $icon-name) {
#warn "'#{$icon-name}' is not a valid icon name";
}
#else {
&--#{$icon-name}::before {
content: $icon;
}
}
}
}
// How to use it
.social-link {
background-color: grey;
#include icons-list($icons);
}
CSS
// CSS Output
.social-link {
background-color: grey;
}
.social-link--facebook::before {
content: "";
}
.social-link--twitter::before {
content: "";
}
.social-link--googleplus::before {
content: "";
}
.social-link--youtube::before {
content: "";
}
This code was taken from my own answer in the following post but the answer is a case use of #each :)
Hope this help you
Example making a theme with css variables with fallback color
see codepen css variables
// VARS (FOR FALLBACK)
// -------------------
$theme-base: #70c1ac;
$theme-base-aa: adjust-color($theme-base, $blue: 125);
// PROCESSED THEME
$theme-color: $theme-base;
$theme-color-dark: darken($theme-color, 20%);
$theme-color-light: lighten($theme-color, 20%);
$theme-color-mixed: mix(#fff, $theme-color, 75%);
$theme-color-trans: transparentize($theme-color, .4);
// PROCESSED SECONDARY
$theme-color-aa: $theme-base-aa;
$theme-color-aa-dark: darken($theme-color-aa, 20%);
$theme-color-aa-light: lighten($theme-color-aa, 20%);
$theme-color-aa-mixed: mix(#fff, $theme-color-aa, 75%);
$theme-color-aa-trans: transparentize($theme-color-aa, .4);
$theme-colors: (
"aa-dark": $theme-color-aa-dark,
"aa-light": $theme-color-aa-light,
"aa-mixed": $theme-color-aa-mixed,
"aa-trans": $theme-color-aa-trans,
aa: $theme-color-aa,
dark: $theme-color-dark,
light: $theme-color-light,
mixed: $theme-color-mixed,
theme: $theme-color,
trans: $theme-color-trans,
);
#mixin themeColor ($prop, $color: null) {
#if ($color) {
#{$prop}: map-get($theme-colors, $color);
#{$prop}: var(--theme-color-#{$color})
} #else {
#{$prop}: map-get($theme-colors, theme);
#{$prop}: var(--theme-color);
}
}
#mixin setThemeColors($base1: "", $base2: "") {
// BASE THEME COLORS
$color-base: $theme-base;
$color-aa: $theme-base-aa;
#if ($base1) {
$color-base: $base1;
$color-aa: $base2;
}
// PROCESSED THEME COLORS
$color-aa-dark: darken($color-aa, 20%);
$color-aa-light: lighten($color-aa, 20%);
$color-aa-mixed: mix(#fff, $color-aa, 75%);
$color-aa-trans: transparentize($color-aa, .5);
$color-aa: $color-aa;
$color-dark: darken($color-base, 20%);
$color-light: lighten($color-base, 20%);
$color-mixed: mix(#fff, $color-base, 75%);
$color-trans: transparentize($color-base, .5);
// CSS VARIABLES
--theme-color-aa-dark: #{$color-aa-dark};
--theme-color-aa-light: #{$color-aa-light};
--theme-color-aa-trans: #{$color-aa-trans};
--theme-color-aa: #{$color-aa};
--theme-color-dark: #{$color-dark};
--theme-color-light: #{$color-light};
--theme-color-mixed: #{$color-mixed};
--theme-color-trans: #{$color-trans};
--theme-color: #{$color-base};
}
:root {
#include setThemeColors($theme-base, $theme-base-aa);
}
body {
#include themeColor("background","mixed");
font-size: 2rem;
}
ul {
list-style: none; /* Remove default bullets */
}
ul li::before {
content: "\2022"; /* Add content: \2022 is the CSS Code/unicode for a bullet */
#include themeColor("color","dark");
font-weight: bold; /* If you want it to be bold */
display: inline-block; /* Needed to add space between the bullet and the text */
width: 1.2em; /* Also needed for space (tweak if needed) */
margin-left: -.8em; /* Also needed for space (tweak if needed) */
}
li {
#include themeColor("color", "light");
#include themeColor("background", "aa-dark");
}
Why pick one when you can have them both.
_variables.scss
$color0 : white;
$color1 : red;
$color2 : green;
$color3 : blue;
_lists.scss
#use "variables";
#use "sass:map";
#use "sass:meta";
#use "sass:list";
#function dynamic($variable){
$i: 0;
$list: ();
#while(variable-exists($variable + $i)){
$list: list.append($list, map.get(meta.module-variables(variables), $variable + $i));
$i: $i + 1;
}
#return $list;
}
$colors: dynamic('color'); // white red green blue
Import both into your scss files and use the list when you need to loop and the variables for shorthand when applying styles.
map-get is used for getting css value from more kind of object.
suppose you have $param where you have defined multiple properties and now you want to assign. you can use it in following ways -
color: map-get($params, "color");
Where else simple variable holds only single value
map-get to get css value from object holding multiple values whereas
variable to hold single value