Adding dark mode to my static Jekyll site

Published on under the IndieWeb category.

A screenshot of my website home page themed with a dark mode theme

I use dark mode extensively across the web and indeed across my computer. The editor in which I am writing this blog post, Typora, is set up with a dark mode theme. My Ubuntu desktop has a dark mode theme. When I see the option to enable dark mode, I like to enable it. Anecdotally, I find dark mode easier on the eyes than bright modes.

I have recently been experimenting with applying a dark mode theme to my site. I wanted to add a dark theme: (i) to experiment more with CSS and; (ii) to make my site more accessible to those who prefer darker colour schemes. In this blog post, I am going to briefly explain how I added dark mode to my Jekyll site.

Writing dark mode styles

First, I needed to write the CSS styles that would power my dark mode. This took a bit of experimentation because I wanted my dark mode to be easy on the eyes as well as aesthetically pleasing. I arrived at a soft grey as the background, common in dark modes, and white for the text.

I had to consider accessibility for this project, too. After facilitating feedback from the IndieWeb community, I was made aware that my early designs may pose a problem for those that have trouble distinguishing red and green. I went back to the drawing board and came up with some new style rules. To the extent I am aware, the style rules I came up with are accessible (if you encounter any issues, please do contact me). The rules are:

  • Links should be green.
  • Headings should be orange.
  • Text should be a light grey that has sufficient contrast so as not to conflict with the background.

In CSS terms, I came up with this colour palette:

  • Links: lightgreen.
  • Headings: orange.
  • Text: #ccc.
  • Background colour: #1F2937 (the same background that micro.blog uses).

With this colour palette ready, I had to go around my site and see where I would need to apply my styles. Changing the background, links, and headings was easy enough. But text was another challenge. I forgot that my colour style, applied only to paragraph (p) tags, would not apply to some text elements like form labels and description lists. An audit across the site helped me pick up these issues.

Here is how the dark mode looks:

A screenshot of my blog home page with dark mode enabled

Switching to different modes

I initially thought that I would apply dark mode only to those who have set their browser or operating system to prefer dark mode styles when available. However, after some thought, I decided a toggle would be more appropriate, so as to give all website visitors control over the theme of the site. I added some JavaScript so that the default style is dark mode for those who have set their browser or operating system to request dark mode styles.

I implemented the following algorithm to set the mode of a page:

  1. Hide dark mode.
  2. If a visitor prefers the “dark” colour scheme, set style to dark.
  3. If a localStorage “darkmode” item is set to “true”, use dark mode.
  4. If a localStorage “darkmode” item is set to “false”, do not use dark mode.
  5. If no localStorage “darkmode” item is set, do nothing.

Dark mode is hidden by default so that my JavaScript code can enable dark mode as appropriate. One limitation with my design is that it uses JavaScript to detect if a user prefers a dark colour scheme (step #2) designed above. This is to avoid having to duplicate my dark mode CSS code into a prefers-color-scheme class. I may revise this at a later date.

The algorithm above can be translated into this code:


if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    document.getElementById('darkmode').media = "all";
}

if (localStorage.getItem('darkmode') == "true") {
    document.getElementById('darkmode').media = "all";
} else if (localStorage.getItem('darkmode') == "false") {
    document.getElementById('darkmode').media = "none";
}

The “media” property describes how the CSS in an inline style sheet should be applied. “all” means the styles should be displayed. “none” means the styles should be hidden. This approach allows me to manipulate what CSS should load using JavaScript.

Next, my code injects a link into my navigation bar which says either “Light Mode” or “Dark Mode” depending on the colour scheme a user has selected. I inject the link so that the link does not appear if a browser cannot render the JavaScript that powers the dark theme. If I kept the link in the HTML code of the web page, the link would appear and do nothing if, for some reason, the JavaScript for the project could not be loaded.

This method of development is called “progressive enhancement” where one adds features to a site if JavaScript is enabled in a way that does not cause a page to load or render improperly if JavaScript is disabled.

The link injection translates into this code:


var navigation = document.getElementById("top_navigation");
var ul = navigation.getElementsByTagName("ul")[0];

var li = document.createElement("li");
var dark_style = document.getElementById("darkmode");

if (dark_style.media === "all") {
    li.innerHTML = "Light Mode";
} else {
    li.innerHTML = "Dark Mode";
}

ul.appendChild(li);

This code:

  1. Finds my navigation bar.
  2. Finds the unordered list (ul) element in my navigation bar.
  3. Creates a new list item.
  4. Sets the value of the list item to “Light Mode” or “Dark Mode” with a link, depending on the theme set. If dark mode is set, dark_style.media will be equal to “all”, otherwise dark_style.media will be equal to “none”.
  5. Adds the link into my navigation bar.

You will notice a function referenced in the above code called “toggleTheme().” This function is what actually changes the page theme. It works in a similar way to how my code decides whether to show the text “Light Mode” or “Dark Mode” in the navigation bar. Let’s take a look at the function:


function toggleTheme() {
    if (dark_style.media === "all") {
        dark_style.media = "none";
        li.innerHTML = "Dark Mode";
        localStorage.setItem('darkmode', 'false');
    } else {
        dark_style.media = "all";
        li.innerHTML = "Light Mode";
        localStorage.setItem('darkmode', 'true');
    }
}

Here is how the theme toggle looks on my blog (pay attention to the last link in the image):

A screenshot of my blog navigation bar with a link to switch themes

If my site is set to show the dark mode theme, clicking the toggle link described above will hide all of the dark mode styles and set the “darkmode” item in localStorage to false. localStorage is used to track what theme has been applied.

If dark mode is enabled, this function will disable darkmode and save this preference to localStorage. If dark mode is disabled, the function will enable darkmode.

Wrapping up

This project has been a fun way to explore manipulating the contents of a HTML document using JavaScript. My dark mode is designed to be accessible so as many people who view my site can read its contents easily. I have also added the toggle in such a way that the toggle will not appear if JavaScript is not enabled.

I may experiment with other colours for my dark mode in the future. I might make the orange a bit softer so that it does not stand out as much. However, I have not yet made a final decision on what colour to use. The design works as is and is not nearly as bright as the previous design.

If you have feedback on the new dark mode, let me know by sending a webmention to this page or by emailing me at readers@jamesg.blog.

Also posted on IndieNews.

Go Back to the Top