This article is a part of the series "Creating a blog with Remix"
Adding dark mode to a blog - how to avoid flashes of the wrong theme
I've added dark mode to this blog.
I followed this fantastic guide from LogRocket. I'm not going to repeat what they've done there, we'll just mention that they're making using of CSS variables, the @media (prefers-color-scheme: dark)
query, and the package react-responsive
for queries inside of React.
Here's a few additional things that I had to do.
Render conditional images client side.
I'm being a little bit clever with the main blog image on the front page - in light mode I show a sheep during the daytime and in dark mode he's in a dark room illuminated by the laptop screen.
The server-side-rendered first render isn't aware of what the users preferred color scheme is, and so that image would aways come through as the light mode image.
The solution is to do the conditional check on the client side. We display nothing on first render (the server render) and the second render when we know what the user preference is, we display the image.
8export function SheepImage() {
9
10 const isDarkMode = useMediaQuery({ query: "(prefers-color-scheme: dark)" })
11
12 // Because SSR doesn't know what the preferred theme is
13 // We hide the image at first so you don't get the wrong one
14 const [ready, setReady] = useState(false);
15
16 useEffect(() => {
17 setReady(true);
18 }, [])
19
20 return <div className="sheep-image-wrapper">
21 {ready && <img src={isDarkMode ? dark : light} alt="A black sheep typing at a computer" />}
22 </div>
23}
This introduces a slight problem of having an image pop of the image suddenly appearing. To mitigate this I use a CSS animation to make it fade in over 100ms.
I later removed this, I found the image fade in jarring. I opted for a simpler solution of displaying both images, and using a CSS media query on preferred theme to determine which one is displayed.
Conditionally use stylesheets
This blog uses highlight.js
the plain markdown codeblocks. HighlightJS provides light and dark themes separately - they don't provide user responsive themes.
Using the user preference via react-responsive to determine the theme would have the same issue as the conditional rendering of the image above, the server isn't aware of the user preference, and you'd get a style pop when the page loads and the theme switches.
Fortunately there's a very handy way to conditionally apply CSS. Per this very handy issue thread - I import both themes, conditionally choose the one matching the user's preference with a media="(prefers-color-scheme: dark)"
attribute on the link tag.
Lines 57 to 60 in 72ee472
57
58 { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css", media: "(prefers-color-scheme: dark)" },
59 { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css", media:"(prefers-color-scheme: light)" },
60
Other miscellaneous updates
I needed to update my utterances config
27 // define if you want to use dark or light theme.
28 commentScript.setAttribute('theme', 'preferred-color-scheme')
I also made updates to react-github-permalink to have it include a user preference responsive theme.
Questions? Comments? Criticisms? Get in the comments! 👇
Spotted an error? Edit this page with Github