Did you notice anything different about the blog? Well, I introduced a dark theme. Can you find how to change the theme by just looking at my blog? No? If you’d like to switch the theme to dark or light mode, read the last section of this post. Now, some of you may know that I migrated my blog from WordPress to Jekyll. Jekyll for those of you who don’t know, is a tool that transforms markdown text into static websites and blogs. If you are left wondering what all that mumbo jumbo was about, then it will perhaps serve you better to skip this post :). For some time now, I have been wanting to add a dark mode for my blog. But it never came up high enough in priority to give it my attention. That changed with the new year. I decided to add dark theme to my blog in 2024.


I am the kind of person who likes to do today what can be done tomorrow, once I set my mind on a goal. So here I am in early 2024 finishing up this task. Before I began doing my own way, I searched the internet to see if someone already solved the dark theme feature in Jekyll. After a bit of searching I did not like most of the solutions. I wanted something simple and consistent. Thankfully, the theme I use for Jekyll which is called “minima” has a dark theme. But it is currently in beta and not released yet as a stable version.


There are a couple of ways to use the beta version on your blog, but I found that copy-pasting the latest code suits my needs at this point. So I basically copied all the code under _sass from the github repo and added it to my code. Then I edited the Gemfile and changed the line gem "minima", "~> 2.5" to gem "minima".


Next, I just needed to add the new skin to my _config.yml to use dark mode.

minima:
  skin: dark


Finally I adjusted my main.scss to match the colors I wanted in dark and light theme.

$lm-text-color: #111 !default;
$lm-brand-color: #666 !default;
$lm-table-header-bg-color: lighten($lm-brand-color, 50%) !default;
$lm-table-zebra-color:     lighten($lm-brand-color, 55%) !default;

$dm-text-color: #ccc !default;
$dm-brand-color: #ddd !default;
$dm-table-header-bg-color: darken($dm-brand-color, 65%) !default;
$dm-table-zebra-color:     darken($dm-brand-color, 70%) !default;
$dm-table-header-border:   darken($dm-brand-color, 50%) !default;
$dm-table-border-color:    darken($dm-brand-color, 50%) !default;

$content-width:    800px !default;
$sidebar-width:    240px !default;

@import
  "minima/skins/local",
  "minima/initialize";


With that and a few more changes to my main.scss files to use the respective colors, I could get the theme right. Basically, where ever you need a certain color say text color, just use text-color (without the lm- or dm- prefix). For example, I wanted the background color of code highlights to be the table zebra color. So I added the following to my main.scss file.

.highlight {
  .highlighter-rouge & {
    background-color: $table-zebra-color;
  }
}


There is one problem with this approach though. All my users will only get a dark theme. What I really wanted was my blog to respect the user’s system theme. So if your OS theme is dark then show the blog in dark mode, otherwise show the theme in light mode. It only required one simple change to _config.yml. Change the skin from dark to auto and you are done!


But of course the perfectionist nut that I am, I wanted to give some control to the user too. What if the user has a dark system theme, but wants to read my blog in light mode? I wanted to call the skin local. So I simply copied the auto.scss file under _scss/minima/skins to local.scss in the same directory. Next, I changed the skin from auto to local in _config.yml. Finally I used data attributes data-theme to apply the right themes. Which means I removed all the code that falls outside of @if $color-scheme-auto {. Then I removed the that if condition as well.


If there is no data-theme attribute or if data-theme="light", then use light theme. If data-theme="dark" then use light theme. A snippet is shown below.

// default theme is light
:root, :root[data-theme="light"] {
  --minima-brand-color: #{$lm-brand-color};
  --minima-brand-color-light: #{$lm-brand-color-light};
  ...
}

@include lm-highlight;

// dark mode
:root[data-theme="dark"] {
  --minima-brand-color: #{$dm-brand-color};
  --minima-brand-color-light: #{$dm-brand-color-light};
  ...

  @include dm-highlight;
}

$brand-color: var(--minima-brand-color);
$brand-color-light: var(--minima-brand-color-light);
...


How do we let the user change the theme then? I added a simple clickable image to the header of my blog which you can use to change the theme. So I added the follow html code to header.html.

<div style="position: relative; display: inline-block;">
  <img src="/assets/img/blog header.jpg" alt="blog header image"/>
  <img src="/assets/img/toggle_theme.png" onclick="toggleTheme()" style="position: absolute; bottom: 8px; right: 8px; cursor: pointer;"/>
</div>


Next I added this script to the same file.

// determines if the user has a set theme
function detectColorScheme() {
  // use system default theme if available, or default to "light"
  var theme = window.matchMedia 
      && window.matchMedia("(prefers-color-scheme: dark)").matches 
      ? "dark" : "light";
  // local storage is used to override system theme settings
  if (localStorage.getItem("theme")) {
    theme = localStorage.getItem("theme");
  }
  document.documentElement.setAttribute("data-theme", theme);
}

function toggleTheme() {
  localStorage.setItem("theme", 
      document.documentElement.getAttribute("data-theme") == "dark"
          ? "light" : "dark");
  detectColorScheme();
}

window.matchMedia("(prefers-color-scheme: dark)")
    .addEventListener("change", e => {
      // remove any user override settings
      localStorage.removeItem('theme');
      detectColorScheme();
});

detectColorScheme();


When the page first loads, it calls detectColorScheme(). Over there, we first check to see if the browser supports matchMedia. If so, then check if the system setting is in dark mode by checking prefers-color-scheme. In that case, we use dark mode. Otherwise use light mode. Then check to see if theme is stored in local storage. If there is a theme, then it will be used to set the data-theme attribute.


When a user clicks on the icon to change theme, toggleTheme() will be called. It will just toggle the theme based on the currently set data-theme attribute. We also add a hook to watch changes to system theme after the page has loaded. If the user changes the system theme, our data-theme attribute will match it with system theme removing any overrides set by the user for the blog.


How to change the theme

If you don’t like the current theme which is set depending on your system theme, you can change it by clicking on in the blog header (see picture below).


Changing the blog theme


Please let me know if you see any issues with the new color scheme by posting a comment below. Hope you enjoy the dark theme.