CSS variables in Angular: Part 1. Forget about SCSS variables
How to make styling of all elements easy and maintainable
SCSS variables
In my career, I’ve managed to work on a lot of projects that used styling via SCSS
, LESS
, or Stylus
. One of the main advantages of favoring them over CSS
, besides “cascading”, was “variables”
They enabled:
- To respect the DRY principle
- To centralize shared variables for reuse via
@import
or@use
- To create more abstract styles without hardcoding
- To synchronize variable names with Figma tokens
- To perform simple arithmetic and complex operations on variables
- Variables did not go directly into the build, but were compiled into actual CSS values
Example of implementation
Let’s realize a basic example of SCSS variables
application
Variables
$primary: #d75894;
$secondary: #9b3dca;
$background: #fafafa;
Styling of elements
@use 'variables';
body {
background: variables.$background;
h1 {
color: variables.$primary;
}
p {
color: variables.$secondary;
}
}
It’s simple and intuitive 👍.
New theme
But let’s say we need to add support for a dark
theme for the current style scheme due to business requirements. How do we implement it? 🧐
Experienced developers 🤓 will tell you that you should use mapping to substitute the correct value into the selector from each theme. In this way, we will provide extensibility for new themes.
Let’s change our simple variable list to a color theme map
$themes: (
"default": (
primary: #d75894,
secondary: #9b3dca,
background: #fafafa,
),
"dark": (
primary: #5e84ff,
secondary: #0dd0ff,
background: #1b1918,
)
);
Themes applying
@use "variables";
@use "sass:map";
@mixin setTheme($theme-map) {
background: map.get($theme-map, "background");
h1 {
color: map.get($theme-map, "primary")
}
p {
color: map.get($theme-map, "secondary")
}
}
body {
@each $theme-key in map.keys(variables.$themes) {
&[data-theme="#{$theme-key}"]{
@include setTheme(map.get(variables.$themes, $theme-key))
}
}
}
Result
Yay, we’ve achieved the requested behavior.
But at what cost?
Problems
As you can see from this small example, the amount of code to support the new dark
theme has increased, and the old scheme had to be refactored.
Another disadvantage is obtained is the increase in the resulting CSS
file due to having multiple themes simultaneously.
And hence its size, which takes time 📉 to download and parse.
body[data-theme=default] {
background: #fafafa;
}
body[data-theme=default] h1 {
color: #d75894;
}
body[data-theme=default] p {
color: #9b3dca;
}
body[data-theme=dark] {
background: #1b1918;
}
body[data-theme=dark] h1 {
color: #5e84ff;
}
body[data-theme=dark] p {
color: #0dd0ff;
}
As a consequence, with the growth of the code base and the requirement for stylization, we will get:
- Increasing complexity of style architecture in the development of individual components
- Maintenance and development of new features will be harder every time
- Constant refactoring and need for regression screen tests
These problems will undoubtedly affect the speed of development, the quality of the result, and the convenience of developers.
Unfortunately, many libraries developed using SCSS
, such as @angular/material
, encourage this approach. Many developers are unaware of the recommended ways of styling via mixins, which leads to anti-pattern styling as well as the use of ::ng-deep
Those who have ever upgraded Angular in conjunction with
@angular/material
will understand UI problems in regression tests after upgrade if you don’t use the recommended way of component customization
So what’s the solution to this problem?
СSS-variables
Main advantages and features:
- Just like
SCSS variables
allow us to declare one-time reusable values to comply with DRY - We don’t need to manually synchronize with Figma tokens, as they can be provided immediately in the final version from Figma
- Have a global scope and do not require
@import
or@use
to be used - They are native and supported by most browsers
- They are not compiled and remain a part of the resulting
CSS
file. As a consequence, you can access the actual value of a particular variable in runtime - When publishing our components, customizing them is made noticeably easier by providing one new
CSS variable
file, instead of creating a lot of mixins as API-customization
Refactoring the old customization system
Convert SCSS variables
to CSS variables
and declare them in :root
:root {
[data-theme="default"] {
--primary: #d75894;
--secondary: #9b3dca;
--background: #fafafa;
}
[data-theme="dark"] {
--primary: #5e84ff;
--secondary: #0dd0ff;
--background: #1b1918;
}
}
Used as example. This option is not recommended and it is better to use a separate file for each theme with its own
:root
New styling scheme for CSS variables
body {
background: var(--background);
h1 {
color: var(--primary);
}
p {
color: var(--secondary);
}
}
Result
How does the resulting CSS file look like?
body {
background: var(--background);
}
body h1 {
color: var(--primary);
}
body p {
color: var(--secondary);
}
We got an end-to-end style file 🎉. You can have as many color themes as you want, and the file and its original size will not increase.
Conclusion
CSS variables
allow us to create abstract component styles that don’t work with values directly.
We no longer need to create an alternate style file with all the selectors, but just provide a new list of CSS variables
to rely on. Essentially, css variables are our style settings, and the more we rely on them, the more flexible we are in customizing the interface to fit individual themes and presets.
Even if you don’t need support for new themes now, this will be a useful option for the future.
SCSS variables
are better used within the SCSS
syntax for internal functions and mixins, selector generation
Resources
Other parts
- CSS variables in Angular: Part 2. ng-deep is no longer needed
- CSS variables in Angular: Part 3. Charts