We gave the toggle an id #color-scheme-toggle. We will change the image src
to "light" and alt
to "toggle light mode" if the user clicks and toggles to light mode, and vice versa.
Note that you can replace the image with your own dark and light mode images. It is okay to use text buttons too.
We will handle the toggle click event in a moment.
We will style our website by using these two approaches:-
prism-dark.css
and prism-light.css
.body
tag when the color scheme has changed. In our case, we will append the dark-mode
CSS class in the body
tag when it's in dark mode.<!-- src/_includes/base.layout.njk -->
<html>
<head>
<!-- Not every page need syntax highlight -->
{% if prism %}
<link id="prism-css" rel="stylesheet"
href="assets/css/prism-light.css">
{% endif %}
</head>
<!-- we will toggle the dark-mode CSS class -->
<body class="">
...
</body>
</html>
/* src/_includes/base.layout.css */
body {
background-color: white;
color: black;
}
body.dark-mode {
background-color: black;
color: white;
}
When the user clicks on the color scheme toggle, we need to:
src
and alt
href
if it existsWe will also fire a custom event. Let's name it "colorSchemeChanged" because we want to decouple the page specific changes.
For example, my profile photo in home page changes when the color scheme changes. It is specific to that page, and I don't need that logic in other pages.
By firing a custom event, each page can listen to the event and implement page specific updates independently.
Here is the code.
// src/_includes/base.layout.css
{
const bodyEl = document.body;
const toggleEl = document.querySelector('#color-scheme-toggle');
const prismEl = document.querySelector('#prism-css');
const DARK = 'dark';
const LIGHT = 'light';
const COLOR_SCHEME_CHANGED = 'colorSchemeChanged';
toggleEl.addEventListener('click', () => {
const isDark = bodyEl.classList.toggle('dark-mode');
const mode = isDark ? DARK : LIGHT;
sessionStorage.setItem('jec.color-scheme', mode);
if (isDark) {
toggleEl.src = toggleEl.src.replace(DARK, LIGHT);
toggleEl.alt = toggleEl.alt.replace(DARK, LIGHT);
if (prismEl) prismEl.href = prismEl.href.replace(LIGHT, DARK);
} else {
toggleEl.src = toggleEl.src.replace(LIGHT, DARK);
toggleEl.alt = toggleEl.alt.replace(LIGHT, DARK);
if (prismEl) prismEl.href = prismEl.href.replace(DARK, LIGHT);
}
toggleEl.dispatchEvent(new CustomEvent(
COLOR_SCHEME_CHANGED, { detail: mode }
));
});
}
{
// init...
}
If you want to learn more about custom event, here is the documentation of Custom Event API by MDN.
Next, we can update our init
function from just now. Trigger the color scheme toggle click event when it is in dark mode.
// src/_includes/base.layout.css
...
{
function init() {
...
if (mode === DARK) {
// add this line
document.querySelector('#color-scheme-toggle').click();
}
}
...
}
Here is an example on how you could listen to the colorSchemeChanged
custom event. We will update our profile photo's src
on the home page when the color scheme changes.
Prepare two profile photos, one for light and one for dark mode. Here is the HTML:
<!-- src/root/index.njk -->
<img id="profilePhoto" src="assets/img/me-light.jpg">
Here is the code:-
// src/root/index.js
{
const toggleEl = document.querySelector('#color-scheme-toggle');
const DARK = 'dark';
const LIGHT = 'light';
const COLOR_SCHEME_CHANGED = 'colorSchemeChanged';
toggleEl.addEventListener(COLOR_SCHEME_CHANGED, (e) => {
const isDark = e.detail === DARK;
const imgEl = document.querySelector(`#profilePhoto`);
const mode = [DARK, LIGHT];
if (isDark) mode.reverse();
imgEl.src = imgEl.src.replace(mode[0], mode[1]);
});
}
Sometimes, the image's colors might be too bright when displayed on a dark background. We can adjust the image colors with CSS.
This website applies the CSS below for all images:
.dark-mode img {
filter: grayscale(30%);
}
It is good to change the color schemes gradually, providing users with visual feedback and a pleasant experience. This site applies background color transitions when the color scheme changes.
/* src/_includes/base.layout.css */
body {
background-color: white;
color: black;
/* add this line */
transition: background-color 300ms ease-in-out 0s;
}
body.dark-mode {
background-color: black;
color: white;
}
The Chrome team has developed a custom element that allows you to easily insert the Dark Mode 🌒 Toggle on your site.
Take a look at the documentation, NPM install and use it right away... without having to write the most of the custom code above.
# command
npm install --save dark-mode-toggle
This web component is used in the V8 blog. (TLDR - V8 is the JavaScript engine, used in Chrome and Node.js.)
You might be wondering why I did not use that? It is because I only found out after I have shipped my code. 😆
Since my implementation is decent for my scenario, I'll just continue to use mine.
[Updated: Dec 6, 2020] Oh, receiving several requests, I've updated my site to save theme preference in localStorage for longer persistence.
Yay! Dark mode is proudly supported in our website. 😍 Here's the GitHub repo for the code above: jec-11ty-starter. I'll update the repo whenever I write a new post.
If you need a more detailed guide on dark mode in general, including its history, I recommend you to read this article - Hello darkness, my old friend by my colleague Thomas Steiner.
In the coming posts, I plan to write about more on how I built my website with 11ty:
Let me know if the above topics interest you.
That's all. Happy coding!
Have something to say? Leave me comments on Twitter 👇🏼
Follow my writing: @JecelynYeen