Byte Bunny
site owner

Summary:

You may have noticed many websites today support Dark Mode. Dark Mode is the dark color scheme selectable on your mobile device or PC. There are two methods to achieving Dark Mode support on your own website. CSS, and JavaScript.

This tutorial is for JS and CSS, and includes a manual toggle for the user to override their system setting.

How to do it:

We will start by creating two attribute selectors for our Light and Dark themes.

[data-theme="light"] { } [data-theme="dark"] { }

We will need to populate these attribute selectors with variables for each color that is going to change when the color-scheme is changed. For example, --text-color, --background-color.

[data-theme="light"] { --text-color: #000; --background-color: #fff; } [data-theme="dark"] { --text-color: #fff; --background-color: #000; }

Now we need to reference the variables in the elements we want affected. We will use the entire body tag for demonstration.

[data-theme="light"] { --text-color: #000; --background-color: #fff; } [data-theme="dark"] { --text-color: #fff; --background-color: #000; } body { color: var(--text-color); background-color: var(--background-color); }

Make sense so far? The body tag will now reference one of the selected data-themes.

Next, we need to create a button or link in our page that will toggle Dark Mode.

Make sure to add a default value data-theme=light or data-theme=dark to your html tag.

<!DOCTYPE html> <html lang="en" data-theme="dark"> <body> <button type="button" data-theme-toggle="">Toggle Dark Mode</button> </body> </html>

Now it's time for the JavaScript.

function calculateSettingAsThemeString({ localStorageTheme, systemSettingDark }) { if (localStorageTheme !== null) { return localStorageTheme; } if (systemSettingDark.matches) { return "dark"; } return "light"; }

This function will check if localStorageTheme is set. If not, it will check if systemSettingDark is true. If not, it will fall back to the default "light" theme.

function updateButton({ buttonEl, isDark }) { const newCta = isDark ? "Turn dark mode off" : "Turn dark mode on"; buttonEl.setAttribute("title", newCta); buttonEl.innerText = newCta; }

This is for updating our toggle button or link when the theme changes. newCta will be a string that changes whether or not dark mode is enabled. We will use setAttribute to update the buttons tooltip for desktop users. innerText will update the buttons text.

function updateThemeOnHtmlEl({ theme }) { document.querySelector("html").setAttribute("data-theme", theme); }

This function is for updating our data-theme attribute in the <html> tag.

const button = document.querySelector("[data-theme-toggle]"); const localStorageTheme = localStorage.getItem("theme"); const systemSettingDark = window.matchMedia("(prefers-color-scheme: dark)"); let currentThemeSetting = calculateSettingAsThemeString({ localStorageTheme, systemSettingDark });

This section is for when the page loads. We will create a constant binding called button and target the button or link with the [data-theme-toggle] attribute. Then, we will create another constant binding called localStorageTheme which will grab the users theme if they manually changed it. And last, one more constant binding called systemSettingDark to grab the users system color-scheme.

After all that, currentThemeSetting will use the calculateSettingAsThemeString function we made to determine which theme should be used.

updateButton({ buttonEl: button, isDark: currentThemeSetting === "dark" }); updateThemeOnHtmlEl({ theme: currentThemeSetting }); button.addEventListener("click", (event) => { const newTheme = currentThemeSetting === "dark" ? "light" : "dark"; localStorage.setItem("theme", newTheme); updateButton({ buttonEl: button, isDark: newTheme === "dark" }); updateThemeOnHtmlEl({ theme: newTheme }); currentThemeSetting = newTheme; });

This last section is an EventListener for our button. When the button is clicked, newTheme will be set, "theme" will be updated in the users localStorage, the button gets updated (new title and tooltip), and finally the currentThemeSetting is updated to the newTheme.

That's it! Now let's put it all together.

<!DOCTYPE html> <html lang="en" data-theme="dark"> <head> <style> [data-theme="light"]{--text-color:#000;--background-color:#fff;} [data-theme="dark"]{--text-color:#fff;--background-color:#000;} body{color:var(--text-color);background-color:var(--background-color);} </style> </head> <body> <p>Some text</p><button type="button" data-theme-toggle="">Toggle Dark Mode</button> <script> function calculateSettingAsThemeString({ localStorageTheme, systemSettingDark }) { if (localStorageTheme !== null) { return localStorageTheme; } if (systemSettingDark.matches) { return "dark"; } return "light"; } function updateButton({ buttonEl, isDark }) { const newCta = isDark ? "Turn dark mode off" : "Turn dark mode on"; buttonEl.setAttribute("title", newCta); buttonEl.innerText = newCta; } function updateThemeOnHtmlEl({ theme }) { document.querySelector("html").setAttribute("data-theme", theme); } const button = document.querySelector("[data-theme-toggle]"); const localStorageTheme = localStorage.getItem("theme"); const systemSettingDark = window.matchMedia("(prefers-color-scheme: dark)"); let currentThemeSetting = calculateSettingAsThemeString({ localStorageTheme, systemSettingDark }); updateButton({ buttonEl: button, isDark: currentThemeSetting === "dark" }); updateThemeOnHtmlEl({ theme: currentThemeSetting }); button.addEventListener("click", (event) => { const newTheme = currentThemeSetting === "dark" ? "light" : "dark"; localStorage.setItem("theme", newTheme); updateButton({ buttonEl: button, isDark: newTheme === "dark" }); updateThemeOnHtmlEl({ theme: newTheme }); currentThemeSetting = newTheme; }); </script> </body> </html>


Conclusion:

That's how we did it. And now, you can too.

We hope you enjoyed this tutorial and encourage you to go check out some others.