This is Part 2 of the series on Implementing Dark Mode. This was done in open-sauced/open-sauced#1020, and handily demonstrated to me the wealth of learning opportunities in contributing to Open Source. I for one have learned a ton! On this one, I got the opportunity for learning in several areas. I was still pretty new to React (and I still am), so I had not yet used the Context API. For a lot of the same reasons, I hadn't used the styled-components library before.
Throughout the rest of these points, the thing to bear in mind is that for the most part, the app being in dark mode just means that the HTML body
element has a CSS class name including "dark".
One implementation detail that I feel was a win was that the only React component that had any kind of interaction with the ThemeContext was a set of buttons that toggle the theme. I like to think this helps with separation of concerns. Here's a code snippet from the buttons component:
import React, {useContext} from "react";
import ThemeContext from "../ThemeContext";
import {FlexCenter} from "../styles/Grid";
import darkMode from "../images/darkMode.svg";
import lightMode from "../images/lightMode.svg";
import themeAuto from "../images/themeAuto.svg";
function ThemeButtonGroup() {
const [theme, setTheme] = useContext(ThemeContext);
return (
<FlexCenter style={{marginRight:"0.5rem"}}>
<a
style={{margin:"0 .5rem"}}
disabled={theme === "dark"}
onClick={(event) => {
event.preventDefault();
setTheme("dark");
}}>
<img
src={darkMode}
alt="dark mode"
style={{
backgroundColor:(theme === "dark")
? "#ccc"
: "transparent"
}}/>
</a>
// ...
</FlexCenter>
);
}
Another implementation detail was related to coloring of images. Open Sauced makes use of many SVG images, of differing flavors. In the cases where SVG files are in the static assets of Open Sauced (e.g. <img alt="open sauced" className="svg" src={mortarBoard} />
), the coloring of these is controlled using the filter
CSS property. On the other hand, we also make use of @primer/octicons-react.
Here's a sample of one of these icons in component code:
import {SearchIcon} from "@primer/octicons-react";
// ...
<SearchIcon size="large" verticalAlign="middle" className="svg" />
These inject code directly into markup as <svg>...</svg>
, requiring use of CSS property fill
.
Finally here's the CSS code snippet where the <img>
and <svg>
tags are handled differently.
body.dark img.svg {
filter: invert();
}
body.dark svg.svg {
fill: var(--lightestGrey);
}
I referred quite a bit to this article: Color Control of SVGs.
One last fun implementation detail was working with our use of react-loading-skeleton (I love this effect, and I feel it really does work in keeping the user engaged and under the impression of the app working while data loads). To make this effect still work well in dark mode, I took the opportunity to crack open the source, and replicate a few key values as found in this snippet of our CSS.
body.dark .react-loading-skeleton {
background-color: var(--backgroundGrey);
background-image: linear-gradient(
90deg,
var(--backgroundGrey),
var(--grey),
var(--backgroundGrey)
);
}
Again, working on this PR really cemented my personal belief and experience that contributing to Open Source Software can provide amazing opportunities for learning by doing!
Top comments (5)
Here is my solution for dark mode. It looks better.(is it?).
#javascript
#webdev
#tutorial
#css
Dark mode with 1(or few) line of CSS 🌓
Kavindu Santhusa ・ Nov 28 ・ 5 min read
This technique was used in chrome and safari dark mode libraries, they have some adjustments but ultimately fall short due to different light mode contrast settings - it's really good as a generic setting, for example implementing your own cross site dark mode, but from a product perspective potentially hurts users and limits design.
Check out some open source projects doing exactly that: github.com/darkreader/darkreader - darkreader.org/help/en/
Totally agreed.
Thanks for sharing! I will have to remember your techniques here on the next project.
Nice!