Dark Mode and CSS Variables
Implement dark mode in your app today
Dark mode is something that many of us enjoy but not many have had the chance to implement so far. As I’ve seen my fair share of stubborn setups, I want to share with you some key points you should consider before starting or take into consideration if you’re already using it.
I’ll be using the implementation of dark mode/light mode from our Learn By Ear app as an example, which you can check the live version of here.
The Deep Dive
Let’s dive into it.
Our web app just has to have a dark mode, as we want to give the users the possibility to choose whatever fits them most during the time they interact with our product along with other preferences.
Of course, implementing or having in mind that you’ll need to have two themes from the get-go is helpful, but that’s not always the case. The principles I’ll be sharing with you can be used for both — new implementations as well as refactoring. At the end of the day, it’s all about the key points.
Small remark: We chose Neumorphism as our design pattern, meaning our three colours for the light mode and three for the dark mode were already established. I say established because of the way the effect of Neumorphism works, but I won’t focus on that part here, as it’s specific to Neumorphism. If you’re interested, though, you can read about it in detail.
So as I was saying, Neumorphism established these two sets of colours (the screenshots below are from the project in Figma, which we’re using as a software of choice for our design mocks):
And while we already had three colours per mode, we knew we had to at least have two more — for text and a helper colour (to start with). The way we set up those colours was similar to how we chose the three main ones, which resulted in the following:
We first put the three core colours and then followed with the helper colour — Grey — and the one for the fonts — Dark.
I’d like to stop here for a moment: The way Grey and Dark are named is problematic. First of all, we can see that Grey isn’t really grey in the dark mode scenario, and Dark probably would need to be much lighter if we want to use it on dark backgrounds.
This is the biggest mistake I see theme implementers making over and over again — naming the colours for their colours.
What if you add 10 more of them — which include more dark ones, grey ones, and so on? It’ll most likely lead to eventually having a hot mess, not easily (or at all) useable, and forget about creating adjustable or extendable code. What if you want to have a greyscale mode? Or red? See how that can get out of hand pretty fast?
You need to name the colours by the functions they represent.
Let me show you what I mean:
Above, we broke down our colours into three main sections:
- Text
- Elements
- Helpers
We were able to break it down this way by outlining our app’s design sketches and seeing which elements of the page will need to have colours and how they differ from each other.
Elements
Main, Shadow, and Highlight are from Neumorphism’s pattern but renamed; if you check, initially they were Light, Primary, and Secondary, which can be confusing once you start working with it, as Neumorphism uses those colours for shadow and highlight effects.
Text
Main will vary depending on its background (lighter on darker and vice versa). Dimmed as text could also represent a disabled state and may be smaller/secondary.
Helpers
Danger and Success are for visual feedback, letting the user know if they’re right/wrong when they’re learning the app. The colours don’t necessarily need to be red or green (they can be pink and blue or purple and yellow).
We named them as per our main front-end library’s (Bootstrap) naming convention. Then, since our design is pretty minimalistic, we wanted to include one popping colour, which can again vary based on the main element’s vibe, so we named it PopColour.
The Implementation
Implementing this system of naming is so, so, so much more extendable than for example having Black, Blue, and White as names. If you’re still not convinced, keep reading and I’ll show you how it is when you start actually working with it.
Once we got our naming sorted (this doesn’t have to be the final result, but you’ve got the hang of it), let’s set up some native CSS variables (you can read about them in the official docs here):
We set them on a root level, as per the documentation. These are our default values. Now, whenever you want to use them, you can simply do it this way:
But wait, those were only the colours from the light mode, right? That’s right. In a similar way, we declare the dark mode variables:
Wait, what is this [data-theme='dark']
?
This is the key for your theme change! It’ll apply the values of those variables when your html
tag has that attribute <html [data-theme='dark']>
(you can also do this through JavaScript — let me know if you want me to share the code of that too).
And the body?
The body stays exactly the same! And because we named the variables in a meaningful way, without unnecessary attachment to their colour value, you get so much freedom!
The Hard Way
Let me show you how it looks like when you do it the very hard way.
We have a toggle switch on our website for the light/dark mode change, and sometimes it looks like this:
If we were to keep the Black, Blue, and White naming convention, we’d have this as our CSS:
But that’d work only for our light mode, right? So then you’d need to extend it with:
And you’d need to repeat that on every single element that changes! That loses the sense of variables a bit there, and, in general, is just pure madness.
You want your variables to work for your theme, not to just store some information like a safe box.
Let’s go further and say you want to implement a special theme for New Years or for spring — or both!
You need to cover every element and change every colour for every theme. Woah, that’s a lot of every, isn’t it?
I’m pretty sure not many people would like to take on that task, and the amount of errors that can come out of it is just way too big.
Instead, if we implement variables the way I suggested, we’d have only this line of code:
All the assignments of colours to their respective variables will happen in another place (where your other variables are), and once you change the attribute, it’ll do the work for you.
This way you keep all your colours in one place, and you need to add/change them in only one place. A much cleaner state and safer implementation:
Isn’t that much cleaner and more organised?
For those of you who do have an existing setup already, I’d suggest trying to break it into variables with the suggested naming convention first and then applying them throughout the code. Once you’re done with that — you can start adding themes.
Conclusion
In conclusion, don’t create useless work for yourself or your colleagues. This doesn’t mean that every project has to start with variables now— use your own judgment for that.
But if you do start working with themes, then I’d highly recommend sticking to that naming convention rather than just naming colours for their names. (Try a greyscale website with all those black/grey shades. It’s no fun — I can guarantee you!)
Thank you for reading!