DEV Community

Jean Aguilar
Jean Aguilar

Posted on • Originally published at loserkid.io on

Dark theme, different ways to implement it.

When I was first creating this blog, the first feature I wanted for it, was the ability to switch to a light or a dark theme. I remember that light and dark themes were already a thing since, even since I was installing custom ROMS in my galaxy s3 mini with android 4.1. There were some ROMS that provided the functionality to switch to a dark system ui, which was pretty awesome at the time, considering that until last year, we start seeing the feature in stock android and IOs - macOS.

So in this post I would go through on how some websites implement their themes; Most of them allow you to switch by using a toggle or button, some of them persist even if you close the browser and some of them may even choose the theme based on you OS preferences.

Google Fonts.

First I would like to talk about the easiest and straight forward way to do it, I’m going to Google Fonts as the example.

Inspecting the site, you can see that they have a class in their html tag called t-white, and if you press the select background color button and choose the dark option you’ll notice that the html class will change to t-black. The javascript code presumably changes the dom node class attribute to use the opposite t-class that is currently selected and that would change the whole UI. If we take a look a the css (we need to use a tool to beautify it), we can clearly see that they have declared the same classes for the t-black and t-white but with the respective changes to make it work for each theme.

/* Some of the black classes */
.t-black,
.t-black body,
.t-black #main {
    background: #222;
    color: #fff;
    fill: #fff
}

.t-black .fonts-page.is-bordered,
.t-black .fonts-module {
    border-top-color: rgba(255, 255, 255, .4)
}

/* Some of the white classes */
.t-white .fonts-page.is-bordered,
.t-white .fonts-module {
    border-top-color: rgba(0, 0, 0, .4)
}

.t-white,
.t-white body,
.t-white #main,
.t-white .font-preview-headers,
.t-white .font-preview-controls {
    background: #fff;
    fill: #fff
}
Enter fullscreen mode Exit fullscreen mode

What I like of this approach is that you have a dark and light theme that is compatible to every major browser, they are using plain css as it best, even though they write almost the same twice, it ensures cross browser compatibility(which is something very valuable since the others methods don’t provide cross browser compatibility). The page does not persist your selection, so every time you refresh the page, the light theme will be the default one(thus this can be implemented).

Gatsby approach.

Gatsby is a library for developing static websites(this blog is using it!) and their page is pretty awesome. If you check through the project repo you’ll notice that they use a library called theme-ui to manage their styles including the light and dark theme. It is fairly easy to use, you need to a theme object, who’s going to contain colors, typography and layout values. This theme allow you to specify color modes, so you can specify different colors or settings according to your theme mode. Then you can use a custom hook useColorMode that allows you to change or retrieve the current mode value. Gatsby uses it in their DarkModeToggle component. You can see the code here.

Take a look of this example taken from the docs

import React from 'react'
import { useColorMode } from 'theme-ui'
export default props => {
  const [colorMode, setColorMode] = useColorMode()
  return (
    <header>
      <button
        onClick={e => {
          setColorMode(colorMode === 'default' ? 'dark' : 'default')
        }}>
        Toggle {colorMode === 'default' ? 'Dark' : 'Light'}
      </button>
    </header>
  )
}
Enter fullscreen mode Exit fullscreen mode

This code uses the useColorMode hook, that it looks like an useState hook with steroids, which is the one that does the dirty work for you. Check the source to see how awesome it is. Offtopic, but looking through this source code is truly inspirational and gives you a lot of cool ideas.

This approach is the easiest one to setup, and it will do the dirty work for you, because it can persist the choosen theme by storing the value in the local storage, theme-ui does it for you, less things to worry means you code happily. They also provide support for the prefers-color-scheme which we’ll talk later. One of the cons found is browser compatibility, IE11 does not have support for prefers-color-scheme neither for css variables.

Loserkid

Now let’s talk about my blog :p, I wrote the theme for this blog using tutorials on the web, which were mainly focus on using css variables and I leverage overreacted.io code as the inspiration for persisting the theme.

My theme and theme-ui use css variables, they are variables that contain specific values that are defined by us and can be reused across the whole stylesheet. For example I want my button to have a different color, depending on the html class. I can do something like this:

html.light {
  --btnColor: #e66992;
}

html.dark {
  --btnColor: #ffa7c4;
}

button {
  background: var(--btnColor);
}
Enter fullscreen mode Exit fullscreen mode

Every time that our html class changes to dark, the button will update its background to the color that we specify on the html.dark rule, if we change the class to light, then the color is going to change again, using the html.light rule.

The first thing you’ll notice if you have never visit my blog, is that the default theme will match your OS theme, so for example if you have macOS Mojave or after, and you have your system theme default to dark, you’ll see my blog with the dark theme.. This is done by using a cool css property called prefers-color-scheme. According to MDN this media feature detects if the user uses a system light or dark theme.

For example if you add this variable to your browser console:

  var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
Enter fullscreen mode Exit fullscreen mode

You’ll see that you will get an object like this:

{
  media: "(prefers-color-scheme: dark)",
  matches: true,
  onchange: null
}
Enter fullscreen mode Exit fullscreen mode

The object itself contains an attribute called matches, this will return a boolean if the prefers-color-scheme passed (in this case dark), matches your system specification, in my case matches attribute is true because my computer theme is dark, but if yours happens to be using a light theme, it will return true.

If you want to override those settings and let the user to choose the theme and persist it, you will have to use localStorage, because you’ll have to store the user selected value and use it every time the user access the page.

Storing and retrieving the value can be done like this:

// Sets a variable called with key theme and value dark.
localStorage.setItem('theme', "dark");

// Gets the value of the localStorage key called theme
localStorage.getItem('theme');
Enter fullscreen mode Exit fullscreen mode

So your code should have a function that gets and sets the value. It is better to have this on a script that loads before the SPA(in my case react), so having that as window object attributes, makes a lot of sense, since we can call them later. Overreacted.io has an awesome anonymous function that handles every use case. I used it on this blog and I solve a bug that was since day one. The bug was that even though my theme was set to dark in the localStorage, it loaded the light theme first and then update it to the dark one.

Conclusions

  • Using the google fonts approach makes a lot of sense for cross browser support, we can have this working even for IE11 and you know that enterprise wise, a lot of companies are still stuck on IE11 and we have devs have to be fighting against it.
  • Theme UI is a brilliant tool, makes things less stressful an easy to config out of the box. Cons are, browser support, some people are not accustomed to style using jsx and well at the time is only compatible with react.
  • My blog approach, is pretty nice and it is more DIY, just taking the considerations on localStorage, and this property prefers-color-scheme, you’ll be dealing with browser incompatibility, since I used css variables and prefers-color-scheme won’t work.
  • It is important to say, that without counting theme-ui, we can implement any approach we want using plain old js and css, it is just changing a node attribute, the only thing that changes is how the event is handled.

So if I had the opportunity to start over again, I would use theme-ui even though I’m not a huge fan of styling things in the component, I think it makes the setup easier and customizable, if browser support is a huge deal to you, then I’ll guess using a more traditional approach would suit better your needs(yes… having duplicate classes with the inverse color).

(This is an article posted to my blog at loserkid.io. You can read it online by clicking here.)

Top comments (0)