Rowland Ekemezie
Automated System Enthusiast. Addictive Learner. Engineering Lead.
Sass and CSS Dark theme support
A comprehensive guide to implementing dark theme support using Sass and CSS variables, with React hooks for state management.
Introduction
The reason for this post is to highlight the steps I took to add dark theme support and why I took certain decisions. Your mileage will vary so see it as a guide and not the gospel. The starter kit used to bootstrap this site already made a few decisions which I wouldn’t want to change like SCSS which is pretty great. With a CSS-In-Js library like styled-components or emotion, few things would have been easier.
Objectives
- I should be able to share functionality across all components with React hooks
- I should be able to use both Sass and CSS variables together
- I should be able to use either of Sass variables or CSS variables
- User preferred mode should be remembered
- The transition between modes should be smooth
Use React Hooks for the sharable logics
I needed a shared logic for the ToggleSwitch component. One important reason is that the toggle switch will be used ~3 places and it’s a lot cleaner using React hooks for this. The inspiration was gotten from switching off the lights adding dark mode to your react app with context and hooks
I’m basically using createContext and useContext to provide access to isDark and toggleTheme function. I just need to wrap the Layout component with the context provider. Then, with useContext I can access isDark and toggleTheme on every child component. This is pretty much the same way you’d use Provider and Connect functions in redux. Also, I’m adding dark CSS class if the mode is dark and also persisting user mode to the localStorage. Finally, we export ThemeProvider and useTheme.
Here’s how it’s used in the layout component
We can now access isDark and toggleTheme anywhere with useTheme.
Add ToggleSwitch component
Next up is to create a simple and beautiful ToggleSwitch component.
Combine CSS variables and Sass variables
Sass variables were already used in the starter kit I bootstrapped the project from. Like I mentioned in the first paragraph, things would have been a little easier with styled-components, emotion or any of the CSS-in-JS library. However, I don’t want to change the current structure.
First up, I created a sass map of the existing colors to help me convert them to css variables and ensure that nothing depending on them breaks.
color-dark map simply inverts the colors. I’m using sass map-get to access the values specified in the map.
Convert Sass color map to CSS variables
To achieve the goal of inverting between two color maps, I leveraged the root css pseudo class inside _generic.scss file.
This will transpile to:
Create a Sass function for easy accessibility
In order to easily use the CSS variables with the existing styles, I created a sass function which takes the color name and returns a CSS variable.
Sample usage looks like this
Where not to use CSS variables
I wanted to have control over which section of the UI is updated with the toggle and which part remains constant. For instance, Subscribe to Newsletter form, code blocks, scroll to top button, Footer, etc should remain as-is. In those cases, I accessed the color from the map instead of using the values set in the CSS variables.
Color updates not syncing properly
The color of some sections of the page content was updating faster than others. This is obviously a bad UX as different sections update differently. A workaround was adding color to the content wrapper CSS class - content.
Before Fix | After Fix |
---|---|
Dark mode coming to CSS!
With the introduction of dark mode in macOS, Safari Technology Preview 68 has released a new feature called prefers-color-scheme which lets us detect whether the user has dark mode enabled with a media query.
Mark Otto described how we can start using prefers-color-scheme today in order to create themes that dynamically adjust to the new user setting.
Conclusion
There’s an obviously good reason to add dark mode support to the modern day applications. However, such support might be daunting for a legacy codebase. I hope you were able to pick up a few ideas from this article and the lessons learnt.