CSS custom properties (CSS variables) with SCSS
While working on my personal SCSS boilerplate, Atomic Bulldog. I wanted to implement a strict set of variables using maps. The goal is to create a consistent design system and to be able to use CSS variables for easy theming.
The base structure of my variable folder:
_aspect-ratios.scss
_box-shadow.scss
_breakpoints.scss
_colors.scss
_container-sizes.scss
_font-families.scss
_font-sizes.scss
_grid.scss
_root-classes.scss
_spacers.scss
_z-index.scss
When looking on how to implement CSS variables in SASS, I found this great article, CSS4 Variables and Sass, from Jake Albaugh. It helped me to create this template for each variable file:
In settings.scss
, we have a flag to enable the use of CSS variables globally.
$use-css-var: true;
In our variable file, I follow this template:
// ------------------------------
// Sass Variables
// ------------------------------
$variable-prefix: --variable-; // CSS Variable prefix
$variables: (
val1: 1,
val2: 2,
val3: 3 // etc.
);
// ------------------------------
// Set variable function
// ------------------------------
@function myVariable($variable, $true-val: false) {
@if $use-css-var == true {
@if $true-val == true {
@return map-get($variables, $variable); //True Val
} @else {
@return var(#{$variable-prefix}#{$variable}); //CSS Var
}
} @else {
@return map-get($variables, $variable); //Disabled CSS Var
}
}
// If $use-css-var == true
// myVariable(val1) => var(--variable-val1)
// myVariable(val2, true) => 2
// If $use-css-var == false
// myVariable(val3) => 3
// myVariable(val2, true) => 2
// ------------------------------
// Set root variables
// ------------------------------
@if $use-css-var == true {
:root {
@each $name, $variable in $variables {
#{$variable-prefix}#{$name}: $variable;
}
}
}
// Output if $use-css-var == true
// :root{
// --variable-val1: 1;
// --variable-val2: 2;
// --variable-val3: 3;
// /*etc.*/
// }
Example for spacer variables:
// ------------------------------
// Sass Variables
// ------------------------------
$spacer-reference: 1rem;
$spacer-prefix: --spacer-;
$spacers: (
0: 0,
1: (
$spacer-reference * 0.25
),
2: (
$spacer-reference * 0.5
),
3: $spacer-reference,
4: (
$spacer-reference * 1.25
),
5: (
$spacer-reference * 1.5
),
6: (
$spacer-reference * 3
),
7: (
$spacer-reference * 6
),
8: (
$spacer-reference * 9
),
9: (
$spacer-reference * 12
)
);
// ------------------------------
// Set spacer function
// ------------------------------
@function spacer($spacer, $true-val: false) {
@if $use-css-var == true {
@if $true-val == true {
@return map-get($spacers, $spacer); //True Val
} @else {
@return var(#{$spacer-prefix}#{$spacer}); //CSS Var
}
} @else {
@return map-get($spacers, $spacer); //Disabled CSS Var
}
}
// ------------------------------
// Set root variables
// ------------------------------
@if $use-css-var == true {
:root {
@each $name, $spacer in $spacers {
#{$spacer-prefix}#{$name}: $spacer;
}
}
}
With deep maps, (Colours):
// ------------------------------
// Sass Variables
// ------------------------------
$color-prefix: --color-;
$color-themes: (
primary: (
base: #4c5c8c,
dark: darken(#4c5c8c, 15%),
light: lighten(#4c5c8c, 15%),
transparent: transparentize(#4c5c8c, 0.5),
contrast: #ffffff
),
secondary: (
base: #212529,
dark: darken(#212529, 15%),
light: lighten(#212529, 15%),
transparent: transparentize(#212529, 0.5),
contrast: #ffffff
),
text: (
base: #212529,
dark: darken(#212529, 15%),
light: lighten(#212529, 15%),
transparent: transparentize(#212529, 0.5),
contrast: #ffffff
),
link: (
base: #4b7e9e,
dark: darken(#4b7e9e, 15%),
light: lighten(#4b7e9e, 15%),
transparent: transparentize(#4b7e9e, 0.5),
contrast: #ffffff
),
success: (
base: #4b9b50,
dark: darken(#4b9b50, 15%),
light: lighten(#4b9b50, 15%),
transparent: transparentize(#4b9b50, 0.5),
contrast: #ffffff
),
danger: (
base: #c65556,
dark: darken(#c65556, 15%),
light: lighten(#c65556, 15%),
transparent: transparentize(#c65556, 0.5),
contrast: #ffffff
),
warning: (
base: #d3b354,
dark: darken(#d3b354, 15%),
light: lighten(#d3b354, 15%),
transparent: transparentize(#d3b354, 0.5),
contrast: #ffffff
),
light: (
base: #efe5cf,
dark: darken(#efe5cf, 15%),
light: lighten(#efe5cf, 15%),
transparent: transparentize(#efe5cf, 0.5),
contrast: #343a40
),
dark: (
base: #343a40,
dark: darken(#343a40, 15%),
light: lighten(#343a40, 15%),
transparent: transparentize(#343a40, 0.5),
contrast: #efe5cf
)
);
// ------------------------------
// Set color function
// ------------------------------
// retrieve color from map with Sass ie. `color(primary, base)`
@function color($color-name, $color-variant: null, $true-val: false) {
// if we enable css var
@if $use-css-var == true {
// we need to return the color value
@if $true-val == true {
// color variant is optional
@if ($color-variant != null) {
// map inception, need two deep
@return map-get(map-get($color-themes, $color-name), $color-variant);
} @else {
// Default color
@return map-get(map-get($color-themes, $color-name), base);
} // if we're only returning the CSS4 variable
} @else {
// color variant is optional
@if ($color-variant != null) {
// map inception, need two names
@return var(#{$color-prefix}#{$color-name}-#{$color-variant});
} @else {
// Default color, one name
@return var(#{$color-prefix}#{$color-name}-base);
}
}
} @else {
// color variant is optional
@if ($color-variant != null) {
// map inception, need two deep
@return map-get(map-get($color-themes, $color-name), $color-variant);
} @else {
// Default color
@return map-get(map-get($color-themes, $color-name), base);
}
}
}
// ------------------------------
// Set root variables
// ------------------------------
@if $use-css-var == true {
:root {
@each $name, $color in $color-themes {
@if type-of($color) == "map" {
@each $subname, $subcolor in $color {
#{$color-prefix}#{$name}-#{$subname}: $subcolor;
}
} @else if type-of($color) == "color" {
#{$color-prefix}#{$name}: $color;
}
}
}
}