Theming with CSS variables in RGBA
CSS variables are pretty well-supported nowadays across browsers. The global feature coverage is above 87.5% at the time of writing.
Theming apps
A not so surprising use-case of CSS variables is theming apps. Think of Twitter for example where you can specify the color of your profile. This color and its shades appear on many elements: links, buttons, borders, highlights, etc. Take a look at my annotated twitter profile:
Themed elements on Twitter (...oh yeah, follow me)
When you customize your profile, you only have to specify one color, the rest is magically taken care of. I like the simplicity and ease of use of this concept, and I wanted something similar when I started thinking about making Pure CSS Material Components themeable. So the question was how to create a palette out of a color?
There are at least a few different answers to this, but I had some restrictions. As I was working on a CSS only library, using JavaScript was out of scope. Another thing was adhering to the Material Design Guidelines as much as possible.
Coding
Most of the time Material specifies using semi-transparent versions of colors. This led me to the rgba()
functional notation which can be used to create "shades" of a solid color through manipulating the alpha channel. The following snippet specifies the text color to be red, but half-transparent:
.text {
color: rgba(255, 0, 0, 0.5);
}
Similar results can be achieved with the opacity property. However it always applies to the whole element (and its children), while a semi-transparent color can be applied on text, border, background, box-shadow, etc. separately.
Next, I extracted a variable for the RGB color from the declaration above. CSS variables can hold any value, and luckily they resolve within the rgba()
functional notation so the following snippet works just fine:
:root {
--my-color-rgb: 255, 0, 0;
}
.text {
color: rgba(var(--my-color-rgb), 0.5);
}
CSS variables also have the fallback feature. So why not use it? It is a great fit for providing default color if the referred variable is undefined. Putting everything together produces something like this:
.text {
color: rgba(var(--my-color-rgb, 255, 0, 0), 0.5);
}
HSLA alternative
The HSL (and HSLA) color space is getting more popular lately, probably because it aligns more with the human color perception. The hsla()
functional notation adds more opportunites to theming: you can take a hue then saturate/desaturate it, lighten/darken it, or make it more transparent. The above described method could look something like this in HSLA:
:root {
--my-color-hue: 0;
}
.text {
color: hsla(var(--my-color-hue, 0), 100%, 50%, 0.5);
}
Although the HSLA alternative would work flawlessly, I didn't use it in my case. RGBA is completing the task perfectly, and maybe developers are more familiar with RGB than HSL.
Demo time!
I wired this whole concept into my component library. So when there's need for customization one would only have to define one or two variables at top level to create a whole new flavor or even a night mode of their app. Try the sliders under "Colors":
Further demos using the component library:
- Material Registration Form (Amber flavor)
- Duplicate Word Finder (Default colored, I used it for proofreading this post :)
- HTML and JSON Aware Pig Latin Translator (Default colored)
Browser Support
If you liked the demos and would like to implement a similar concept, I have some good news. This works in Chrome, Safari, Firefox, Samsung Internet and Edge. However, I should warn you about some difficulties. It turned out that Safari is not handling the CSS variable with fallback in rgba()
in some cases. These cases are the shorthand or shorthandish properties like border
and box-shadow
. The workaround for this is to create some intermediate variable like:
:root {
--my-color-rgb: 255, 0, 0;
}
.text {
--intermediate: rgba(var(--my-color-rgb, 255, 0, 0), 0.5);
border: solid 1px var(--intermediate);
}
CSS variables are also locally scoped, so defining --intermediate
in .text
won't pollute the global scope.
Yeah, but you forgot that IE11 does not support CSS variables!
You might say. Well you can do multiple things about it. First, I suggest reading The End of Life of Internet Explorer 11 . Second, if you are still not convinced then you can inline your variables with PostCSS.
Final thoughts
When I created Pure CSS Material Components I wanted a lean, easy to use, but accurate component library. Making it themeable was a step that could lead to more complexity. I was also aware that Sass, Less or Stylus were already capable of solving this problem, but I think that's an obstacle for developers who don't use these preprocessors. CSS variables helped me keep things simple and stick to a native solution.
Thanks for reading, I hope this post inspires you to try out CSS variables!