The Complete Guide to the Dark Mode Toggle
Tags: tutorial, css, html, webdev
Published: 2020-03-21 13:28:03

*[I originally published this article on my website](https://ryanfeigenbaum.com/dark-mode/)*

You may have noticed that Dark Mode is becoming more and more a thing. In this article, I provide a complete guide to adding Dark Mode to a website.

> Best Practices to Unlock the Dark Side

Recently, Chris Coyier at CSS-Tricks threw down the gauntlet for anyone who was about ["to write a blog post about dark mode."](https://css-tricks.com/lets-say-you-were-going-to-write-a-blog-post-about-dark-mode/) He listed 10 points that would need to be covered:

1.  Explain Dark Mode
2.  Provide a demo ([Jump to the demo](https://royalfig.github.io/dark-mode-demo/))
3.  Explain that Dark Mode can happen at the operating system level itself
4.  Show how JavaScript can know about the OS-level choice
5.  Let the user have ultimate say over color preference
6.  Build a theme switcher, including gotchas
7.  See who else is building Dark Mode toggles
8.  See who else is writing about it
9.  Discuss what is and isn't supported
10.  Make accessibility a main concern

This post aims to cover all 10 points (some better than others). I offer what I've found to be the best practices for implementing a dark mode toggle, and I look forward to refining these practices with suggestions, criticisms, and comments from the community.

## The Order of Operations

1.  [UI/UX Considerations](#1)
2.  [Defining a Dark/Light UI with CSS Custom Properties](#2)
3.  [Toggling between Dark/Light Mode with JavaScript](#3)
4.  [Respecting the OS-Level Color-Scheme Preference (and Letting the User Override That Preference)](#4)

## UI/UX Considerations <a name="1"></a>

Let's define our terms.

<dl>
<dt><strong>Dark Mode</strong></dt>
<dd>A UI that generally uses a darker color palette</dd>
<dt><strong>Light Mode</strong></dt>
<dd>A long-standing but not immemorial default, in which a UI uses a lighter color palette.</dd>
</dl>
---
<figure>![Apple IIe with monochrome display with a dark background and light foreground](https://dev-to-uploads.s3.amazonaws.com/i/61ogpg7w8jvps8jlc70n.jpg)
<figcaption>Dark Mode? Apple IIe and Bank Street Writer by Mark Mathosian</figcaption>
</figure>

Despite the vociferous support of some, Dark Mode is not held to be superior by all. The preference for either mode is subjective. A user might even prefer to use different modes at different times of day or depending on their location. Perhaps you're browsing Reddit in bed beside your trying-to-sleep spouse who complains about the light from your 32-inch laptop. You just switch modes and, oh my Zsh, Dark Mode saves a marriage, again.

### Accessibility

Switching between a dark color palette and a light one involves more than just switching between darker and lighter background colors. Most importantly, you need to ensure that colors on both palettes meet [contrast requirements for accessibility](https://contrastchecker.com/). **Many colors will not be interchangeable between modes.** This means that you may need different primary or brand colors for each palette.

A second consideration is visual hierarchy. Based on Chethan KVS's excellent article on [designing a dark theme](https://blog.prototypr.io/how-to-design-a-dark-theme-for-your-android-app-3daeb264637), which is itself based on the principles of Material Design, the gist is that darker shades should be reserved for surfaces at the back of the page, with lighter and lighter shades used as you approach the front. For example, to indicate that a modal sits on top of the page, its background color should be lighter than the background color of the section behind it.

Given these guidelines, I've found the following strategy useful for constructing dark and light color palettes.

### Surface Colors

Begin with your surface colors, which might include three or more shades that increase in lightness. These will be the background color of your site's body, menus, footers, navigation bars, modals, etc.

### Element Colors

Do the same thing with your element colors, which might include three or more shades that increase in darkness. _Element color_ refers to things like font color, horizontal lines, borders, tables, and anything else that needs to stand apart from the background. The trick here is to ensure that combinations of background and text meet contrast requirements where appropriate.

### Signature Colors

Finally, choose your sets of primary, secondary, etc., colors. Again, ensure that these colors maintain sufficient contrast on all background colors if they're going to be used as a font color.

![Example Dark and Light Color Palettes](https://dev-to-uploads.s3.amazonaws.com/i/hyth6i16a8i3iatav60k.png)

## Defining a Dark/Light UI with CSS Custom Properties <a name="2"></a>

Nice. You now have some beautiful dark and light color palettes. You're probably thinking to yourself, "I better start preparing my acceptance speech for my palette of the year award." If you're not yet at peak color palette, there are tons of generators out there. Three that I'm particularly fond of include [Tailwind's shade generator](https://javisperez.github.io/tailwindcolorshades/#/), the [material design palette generator](https://www.materialpalette.com/), and [ColorSpace's palette and gradient generator](https://mycolor.space/).

> All dressed up and nowhere to go

But what do you do now? How do you get your palettes to work on your website?

Enter [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), which allow you to define variables in CSS. By using the _same_ property name for your dark and light color palettes, you and your users can toggle between color modes.

Since a custom property is limited to the selector in which it's defined, best practice dictates generally defining all properties under the pseudo-selector `:root` so that variables are available globally. To differentiate between whether the light or dark palette is active, use a color-mode custom attribute selector, which we can target with JavaScript.

<figure>![Defining a dark and light color palette with CSS custom properties](https://dev-to-uploads.s3.amazonaws.com/i/d74ix0s6wupnel53ujk6.png)
<figcaption>Defining a dark and light color palette with CSS custom properties</figcaption>
</figure>
---

## Toggling Dark/Light Mode with JS <a name="3"></a>

Palettes are defined. They're in the stylesheet. But nothing's happening yet!

### Define Your Default

We need to tell our document which mode it should be in by writing a custom attribute on the `html` tag. We're going to write a function that allows users to toggle between dark and light mode, but you first need to define a default mode for the site. Should it load in dark or light mode? This choice will be up to you and maybe some user research. The markup below shows us how to set a default color mode.

<figure>![Set your color palette default in HTML](https://dev-to-uploads.s3.amazonaws.com/i/n1nbu4ao1oulz86tooya.png)
<figcaption>Set your color palette default in HTML</figcaption>
</figure>
---
### Create a Color Mode Toggle Button

For the user to toggle between modes, they'll need something to click. There are several clever implementations, which include [cool](https://dribbble.com/shots/7635203-Light-Dark-mode-toggle-switcher?utm_source=Clipboard_Shot&utm_campaign=jenkarapetyan&utm_content=Light%20%2F%20Dark%20mode%20toggle%20switcher&utm_medium=Social_Share) [animations](https://dev.to/ahmadbassamemran/awesome-animation-checkbox-css-toggle-day-night-mode-5dnm) [between states](https://css-tricks.com/a-dark-mode-toggle-with-react-and-themeprovider/) or custom icons. For this article, I'll do something more pedestrian and alternate between two icons, a sun and moon, to represent light and dark mode, respectively. [(SVG icons are referenced here with the `use` method.)](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use)

<figure>![Markup for light and dark mode buttons](https://dev-to-uploads.s3.amazonaws.com/i/i2yb6w8vqpdine2v9z2s.png)
<figcaption>Markup for light and dark mode buttons</figcaption>
</figure>

To show and hide the buttons depending on the current site's state, we can combine the element's class of "dark" or "light" with the site's current state as defined by the "color-mode" HTML custom attribute.

<figure>![Rules to hide elements depending on site's current color scheme](https://dev-to-uploads.s3.amazonaws.com/i/5uha0skiagy5wvluxmo7.png)
<figcaption>Rules to hide elements depending on site's current color scheme</figcaption>
</figure>

We are on fire 🔥. We've got our HTML and CSS in place, our default color mode displays, so now it's time to hook up our JavaScript to make our buttons interactive. There are three pieces to the code.

1.  Define a function to toggle between dark and light modes
2.  Create an event handler that listens for clicks on our sun and moon buttons
3.  Select those buttons in the DOM

The color mode toggle code works as follows. When a user clicks the button, the `toggleColorMode` function looks to see whether the button contains the class "light--hidden." If it does, then it sets the document's color-mode attribute to "light," thereby turning on Light Mode. If not, then it turns on Dark Mode. But that's not all. The function also sets an item in the user's local storage, which sets the selected mode as the user's preference. You can check it out by visiting the Local Storage option in the Storage or Application panel in your browser's developer tools. Since local storage persists almost indefinitely, the user's preference for light or dark mode remains for any return visit the user may make, even after exiting the browser.

<figure>![JS for to toggle Dark or Light Mode](https://dev-to-uploads.s3.amazonaws.com/i/ij3eyc3g35myhc3rjv76.png)
<figcaption>JS for to toggle Dark or Light Mode</figcaption>
</figure>
---
## Respecting User Color-Scheme Preference <a name="4"></a>

Look at how far we've come! We've defined a dark and light color palette that users can toggle between, and our site will even remember a user's last preference.

But preferences can be set at another level altogether, that is, in the operating system itself. You can set your color preference in Windows, MacOS, and Android, with varying levels of support. If a user has gone to the trouble of changing this preference, then it would be great if we could render our site according to that preference on initial load, automatically.

With the CSS media feature, [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme), accessible via JS and CSS, we can. Here's what the code should do. If the user has set an OS-level preference for a color scheme and has **not** set a preference on our site during an earlier visit, then we should respect the OS-level preference and render the site accordingly in light or dark mode. If a site-level preference was set, then we should respect that preference, instead of the OS-level preference. To ensure that this code executes before rendering the page, put it in the `<head>` tag of your page.

<figure>![Ensure that OS-level or site color-mode preferences are respected](https://dev-to-uploads.s3.amazonaws.com/i/yq6vvkx9ww6bwlp6r889.png)
<figcaption>Ensure that OS-level or site color-mode preferences are respected</figcaption>
</figure>
---
## Demo 🎉

The only thing left is to [see it all in action](https://royalfig.github.io/dark-mode-demo/). All the [source code](https://github.com/royalfig/dark-mode-demo) for the demo is also available.

{% github royalfig/dark-mode-demo %}

## A Note on Support

### CSS Custom Properties

CSS Custom Properties or Variables are [supported in 94.68% of browsers](https://caniuse.com/#feat=css-variables). Support is lacking in IE and older browsers. It's easy to supply a fallback in these cases; however, users won't be able to switch between color modes in an unsupported browser, which will be set to whatever you set as the default.

<figure>![Provide a fallback for unsupported browsers](https://dev-to-uploads.s3.amazonaws.com/i/tvk7qocrtrnus44abr3c.png)
<figcaption>Provide a fallback for unsupported browsers</figcaption>
</figure>
---
#### Use a SASS Mixin to Automate Your Fallback

Writing the additional fallbacks is tedious. You can automate the process by using a SASS mixin, which will write the fallbacks for you.

<figure>![SASS Mixin for Providing Fallback Support](https://dev-to-uploads.s3.amazonaws.com/i/9ds0jot1tl9xt6y7zi9j.png)
<figcaption>SASS Mixin for Providing Fallback Support</figcaption>
</figure>
---
### <br>Prefers-Color-Scheme

The prefers-color-scheme media query is [supported in 80.85% of browsers](https://caniuse.com/#feat=prefers-color-scheme). Lack of support should not break anything.

One strategy we can take for those browsers that don't support this feature is to hide the toggle button. To do this, we test to see whether custom properties are supported. If not, we hide the toggle buttons. You can test this out by opening up the demo in IE. It'll default to the light color theme and hide the toggle buttons, since they wouldn't be operative. The updated code is below, transpiled to be ES2015 compatible.

<figure>![Fallback for unsupported browsers in JS](https://dev-to-uploads.s3.amazonaws.com/i/mbz1skiah80wvbankmwy.png)
<figcaption>Fallback for Unsupported Browsers in JS</figcaption>
</figure>

That's it, folks! Suggestions welcomed. [@theroyalfig](https://twitter.com/theroyalfig)

## Resources

- [Let’s Say You Were Going to Write a Blog Post About Dark Mode](https://css-tricks.com/lets-say-you-were-going-to-write-a-blog-post-about-dark-mode/)
- [The Ultimate Guide on Designing a Dark Theme for your Android app.](https://blog.prototypr.io/how-to-design-a-dark-theme-for-your-android-app-3daeb264637)
- [prefers-color-scheme: Hello darkness, my old friend](https://web.dev/prefers-color-scheme/)
- [Material Design - Dark Color Theme](https://material.io/design/color/dark-theme.html)
{% link https://dev.to/ananyaneogi/create-a-dark-light-mode-switch-with-css-variables-34l8 %}