Glencoe River
A New Blog

Hexo and the Dark Mode ... revised

Second approach to implement 'prefers-color-scheme'

While writing my post Hexo and the Dark Mode a few days ago, I thought it would be nice, if I could switch between the normal (light) and the dark theme, I’ve created for the support of the OS-related Dark Mode, even manually. The only thing I needed was a toggle element and a little bit of JavaScript.

Of course, I couldn’t manipulate the media query prefers-color-scheme itself, but introduce a different way by blog uses it. Instead of implementing the media query directly into my CSS (or Stylus) code, I used a root selector, which can be manipulated by JavaScript … something like this:

body {
    background-color: white;
    color: black;

[data-theme="dark"] body {
    background-color: black;
    color: white;

In every Stylus file, where I used @media prefers-dark to achieve the automatic switch by the OS, I changed this line into /[data-theme="dark"] & :

  background-color: color-background
  /[data-theme="dark"] &
    background-color: dark-color-background
    /[data-theme="dark"] &
      filter: brightness(85%)

Some explanations on the Stylus syntax: / means the root of the DOM and & points to the parent selector. Therefore the example will be rendered into this:

#mobile-nav-header {
    background-color: #f1f1f1;
[data-theme="dark"] #mobile-nav-header {
    background-color: #111;

#mobile-nav-header img.avatar {
[data-theme="dark"] #mobile-nav-header img.avatar
    filter: brightness(85%);

Only problem was: the “Root + Parent” Stylus selector doesn’t work in the block variables in the _extend.styl. So I had to copy all theme relevant styles directly to the elements, where such a block was used: @extend <block-name>.

The Toggle Switch

In the footer.ejs I added a toggle checkbox, where I could bind my JavaScript…

<div id="footer-theme">
    <input type="checkbox" id="theme-switch">
    <label for="theme-switch"></label>

… and some CSS in the footer.styl, to style it:

input#theme-switch[type=checkbox] {

input#theme-switch[type=checkbox] + label
  height: 16px
  width: 16px
  display: inline-block
  padding: 12px
  font-size: 22px
  cursor: pointer
    display: inline-block
    font-size: inherit
    text-rendering: auto
    -webkit-font-smoothing: antialiased
    font-family: fa-icon-solid
    content: icon-moon

input#theme-switch[type=checkbox]:checked + label
    content: icon-sun

The icon variables are defined in the _variables.styl like this:

icon-moon = "\f186"
icon-sun = "\f185"

The JavaScript

Everything was now prepared to implement the switching code in JavaScript, which should support a manual switch by clicking the toggle element as well as the automatic switch by the OS.

I wrapped all necessary code into a seperate JS file and placed a reference in the after-footer.ejs, which places it at the bottom of the HTML:

<%- js('js/dark-mode-toggle.js') %>
function detectColorScheme() {
    var theme = "light"; //default

    // get last used theme from local cache
        if(localStorage.getItem("theme") === "dark"){
            theme = "dark";
    } else if(!window.matchMedia) { 
        // matchMedia not supported  
        return false;
    } else if(window.matchMedia("(prefers-color-scheme: dark)").matches) {
        // OS has set Dark Mode
        theme = "dark";

    // set detected theme
    if (theme === "dark") {
    } else {

const toggleTheme = document.querySelector('input#theme-switch[type="checkbox"]');

function setThemeDark() {
    localStorage.setItem('theme', 'dark');
    document.documentElement.setAttribute('data-theme', 'dark');
    toggleTheme.checked = true;
function setThemeLight() {
    localStorage.setItem('theme', 'light');
    document.documentElement.setAttribute('data-theme', 'light');
    toggleTheme.checked = false;

// Listener for theme change by toggle
toggleTheme.addEventListener('change', function(e) {
    if ( {
    } else {
}, false);

// Listener for theme change by OS
var toggleOS = window.matchMedia('(prefers-color-scheme: dark)');
toggleOS.addEventListener('change', function (e) {
    if (e.matches) {
    } else {

// call theme detection

By using the both addEventListener‘s, each switch will be recognized and this approach is capable to support even more themes, just by using different values in the data-theme attribute.

You can interact with this article (applause, criticism, whatever) by mention it in one of your posts, which will be shown here as a Webmention.

In case your blog software can't send Webmentions, you can use this form or send it manually via or Telegraph:


No Webmentions yet...