How To: Toggle Dark Mode

Last updated
  • svelte
  • semantic web

You might have noticed that light/dark mode toggles are everywhere right now - pretty much every popular website allows users to choose which theme they prefer. So how do you add a theme toggle to your Svelte app?

Luckily, it’s pretty easy - here’s what you need to do:

Step 1: Add a Toggle Component

Create a new component, DarkModeToggle.svelte, and add a new checkbox inside it:

<input type="checkbox" on:click={toggleTheme} />

Checkboxes are pretty good choices for toggles like this one because they are widely supported and represent Boolean states. In our case, false represents “light off” (= “dark”), while true represents “on”.

Append the new component to your app.

Step 2: Toggle Themes

Next, we need to implement the toggleTheme handler. Add a script tag to your component:

<script>
  const STORAGE_KEY = 'theme';
  const DARK_PREFERENCE = '(prefers-color-scheme: dark)';

  const THEMES = {
    DARK: 'dark',
    LIGHT: 'light',
  };

  const prefersDarkThemes = () => window.matchMedia(DARK_PREFERENCE).matches;

  const toggleTheme = () => {
    const stored = localStorage.getItem(STORAGE_KEY);

    if (stored) {
      // clear storage
      localStorage.removeItem(STORAGE_KEY);
    } else {
      // store opposite of preference
      localStorage.setItem(STORAGE_KEY, prefersDarkThemes() ? THEMES.LIGHT : THEMES.DARK);
    }

    // TODO: apply new theme
  };
</script>

...

As you can see, there’s quite a lot going on in here:

  • We use localStorage to store a user’s theme preference.
  • We use a media query to figure out whether the user has a theme preference set in their OS.
  • If the user generally prefers dark themes and also picks the dark theme on our website, we do not need to store a preference. The same is true for users who do not use dark themes both generally and on our site. For all others, we store their preference in their localStorage.

Step 3: Apply the Theme

Finally, we need to apply the new theme. Add another function to your file and call it at the end of toggleTheme:

const applyTheme = () => {
  const preferredTheme = prefersDarkThemes() ? THEMES.DARK : THEMES.LIGHT;
  currentTheme = localStorage.getItem(STORAGE_KEY) ?? preferredTheme;

  currentTheme = localStorage.getItem(STORAGE_KEY) ?? preferredTheme;

  if (currentTheme === THEMES.DARK) {
    document.body.classList.remove(THEMES.LIGHT);
    document.body.classList.add(THEMES.DARK);
  } else {
    document.body.classList.remove(THEMES.DARK);
    document.body.classList.add(THEMES.LIGHT);
  }
};

const toggleTheme = () => {
  // ...

  applyTheme();
};

Looks good so far! However, we still don’t handle some edge cases:

  • How do we apply the initial theme when a user first visits our website?
  • What happens when a user changes their system wide theme preference?

We can take care of both situations in onMount:

import { onMount } from 'svelte';

// ...

onMount(() => {
  applyTheme();
  window.matchMedia(DARK_PREFERENCE).addEventListener('change', applyTheme);
});

Last but not least, adapt the checkbox so it’s state matches the theme:

<input type="checkbox" checked={currentTheme !== THEMES.DARK} on:click={toggleTheme} />

Now, all that’s left to do is:

Step 4: Write Custom CSS

We can now write custom styles for dark mode:

@media (prefers-color-scheme: dark) {
  body:not(.light) {
    /* Your stuff here... */
  }
}

body.dark {
  /* And also here... */
}

Aaaaand we’re done! 🎉