
One of the ways you can offer Dark Mode / Light Mode on a website is to totally honor the system preference in CSS alone. So like…
@media (prefers-color-scheme: dark) { } @media (prefers-color-scheme: light) { } @media (prefers-color-scheme: no-preference) { }Code language: CSS (css)That’s fine, I suppose, but it’s not very fun. It’s way more fun to have a UI toggle that users can use! Plus, it’s possible that even if a user has set a preference, they might want to view your site opposite of that preference. So my thinking is that you don’t use those in CSS at all, you use a class, probably right at the document level, indicating which theme is happening:
<html lang="en" class="dark-mode">Code language: HTML, XML (xml)So how do you get that class on there properly? I think we should:
The tech can get complicated! In this article, Paul Armstrong uses an Edge Function to deal with cookie management and Client Hints and such to figure out the correct class, then rewriting the HTML before it arrives at the browser with the correct class. It’s cool, but that’s a lot and I’m kinda scared of it.
I think we can get this done (and, bonus, avoid FART (Flash of inAccurate coloR Theme)) with a more basic client-side JavaScript approach. Part of the trick is running the JavaScript in the <head>, so that the class is in place before a first render. That’ll be render-blocking JavaScript, so let’s keep it as small as possible.
Let’s assume the UI toggle is:
<label for="darkMode">Dark Mode?</label> <input id="darkMode" type="checkbox" checked>Code language: HTML, XML (xml)Here’s my crack at the JavaScript:
<script> const COOKIE_NAME = 'darkmode'; let uncheckBox = false; function disableDarkMode() { document.documentElement.classList.remove('dark-mode'); document.documentElement.style.colorScheme = 'light'; document.cookie = `${COOKIE_NAME}=false; expires=Fri, 31 Dec 9999 23:59:59 GMT;"` } function enableDarkMode() { document.documentElement.classList.add('dark-mode'); document.documentElement.style.colorScheme = 'dark'; document.cookie = `${COOKIE_NAME}=true; expires=Fri, 31 Dec 9999 23:59:59 GMT;"` } if (document.cookie.split(';').some((item) => item.trim().startsWith(`${COOKIE_NAME}=true`))) { enableDarkMode(); } else if (document.cookie.split(';').some((item) => item.trim().startsWith(`${COOKIE_NAME}=false`))) { disableDarkMode(); uncheckBox = true; } else { if (window.matchMedia && (window.matchMedia('(prefers-color-scheme: dark)').matches || window.matchMedia('(prefers-color-scheme: no-preference)').matches)) { enableDarkMode(); } else { disableDarkMode(); } } addEventListener('DOMContentLoaded', (event) => { window.darkMode.addEventListener("click", () => { if (window.darkMode.checked) { enableDarkMode(); } else { disableDarkMode(); } }); if (uncheckBox) { window.darkMode.checked = false; } }); </script>Code language: HTML, XML (xml)With that in the <head>, the <html> element with either have a class dark-mode or it won’t. Now, in the CSS, you don’t use any @media alternations of color whatsoever, you only rely on the class name.
This relies on JavaScript being active, so that could count as a strike against it I suppose. Maybe a little improvement would be only visually revealing the UI toggle when the JavaScript runs. Improvement beyond that would need server-side code to deal with the cookies and classes.
The exact code above though is working pretty well for me as I write this:
No FART.
Related
ncG1vNJzZmibmKe2tK%2FOsqCeql6jsrV7kWlpbGdgZnxyhY6dmKujXaK8pbGMr6CaZZFiwK6ty6WgrKBdqLCztc%2BtZKKmXam1pnnHnpidZZGrvKqwyKeeZp6Rp8Fw