Dark Mode Toggle with Alpine & Tailwind

mike_hasarms

Mike Healy

Posted on June 4, 2021

Dark Mode Toggle with Alpine & Tailwind

Some users prefer darker colour themes to prevent eyestrain, while others like having the choice depending on their lighting conditions. Tailwind makes it easy to specify different colours to use depending on which mode is active.

By default Tailwind responds to the prefers-color-scheme media query, which passes through the users' operating system preference. You can also configure it to alter the colours by inheriting a 'dark' CSS class name from a parent element.
This lets you to build your own UI control to switch modes, so that users can have a different preference for your site than their OS.

To do this, change the darkMode property in your tailwind.config.js file

// tailwind.config.js
module.exports = {
  //...

  darkMode: 'class',
}
Enter fullscreen mode Exit fullscreen mode

Controlling The Active Mode

Now that Tailwind is configured to compile dark mode colours based on the .dark class name, we need a way to toggle that class name. We also need to persist the selection between sessions and page loads.

I'll be using localStorage to store the user's preference and AlpineJS to handle the toggling action. Alpine is a lightweight JS framework that works with your markup and doesn't require a build step.

As well as storing the user's preference we'll need to apply it as early as possible during page rendering to activate their desired mode.

How it Works

  • Tailwind generates classes that are applied when dark mode is activated
  • When our page loads we see if the user has set an explicit choice of mode for our site (using the UI control we provide)
  • If they've asked for dark mode we apply the CSS class
  • If they have not set a preference, but their implicit preference via their OS is for dark mode we apply the CSS class
  • Otherwise we'll fall back to the default light mode
  • When our user makes a specific choice using the on-page UI we'll update the document class and also save their preference in localStorage for subsequent page loads.

UI Control

Here's the code to provide light & dark mode buttons. Alpine uses the x-data property to store the state for our component. x-init runs on setup and syncs our local state with what was previously stored in localStorage.

<div
  x-data="{
      mode: '',
      setColorMode: m => {
          if (m === 'dark') {
              document.documentElement.classList.add('dark')
              localStorage.setItem('colorMode', 'dark')
          } else {
              document.documentElement.classList.remove('dark')
              localStorage.setItem('colorMode', 'light')
          }
      }
  }"

  x-init="() => {
      const m = localStorage.getItem('colorMode');
      if (m !== 'dark' && m !== 'light') return;
      mode = m;
  }"
>
  <button
    @click="mode='light'; setColorMode('light');"
    :class="{'font-bold': mode === 'light', 'font-thin': mode !== 'light' }"
    class="underline px-2 py-1 rounded-md border-white hover:bg-gray-100 dark:hover:bg-gray-800"
    >
    light
  </button>

  <button
    @click="mode = 'dark'; setColorMode('dark');"
    :class="{'bg-gray-700 text-gray-200 border border-solid border-gray-600 font-bold': mode === 'dark', 'font-thin': mode !== 'dark' }"
    class="underline px-2 py-1 rounded-md hover:bg-white dark:hover:bg-gray-800"
    >
    dark
  </button>
</div>
Enter fullscreen mode Exit fullscreen mode

Persisting The Selection

Of course as users browse around your site you don't want them to have to re-apply their choice of colour mode every time they visit a new page. We'll use a bit of JS near the top of our document to reapply their preference as early in the render as possible.

<head>
  <!-- other bits -->
  <link rel="stylesheet" href="css/style.css" type="text/css">
  <script src="detect-mode.js"></script>
</head>
Enter fullscreen mode Exit fullscreen mode
// detect-mode.js
// set initial color scheme

let explicitelyPreferScheme = false
if (window.localStorage) {
    if (localStorage.getItem('colorMode') === 'dark') {
        document.documentElement.classList.add('dark')
        explicitelyPreferScheme = 'dark'
    } else if (localStorage.getItem('colorMode') === 'light') {
        document.documentElement.classList.remove('dark')
        explicitelyPreferScheme = 'light'
    }
}

if (explicitelyPreferScheme !== 'light' && window.matchMedia('(prefers-color-scheme:dark)').matches) {
    document.documentElement.classList.add('dark')
}
Enter fullscreen mode Exit fullscreen mode

This will apply dark mode if our visitor has selected it from the UI control above; or if they have not set any preference but their OS is set to use dark mode.

Live Demo

Test it out here. You can refresh the page and see your preference persist.

This was originally published on my website mikehealy.com.au

💖 💪 🙅 🚩
mike_hasarms
Mike Healy

Posted on June 4, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related