Niallโ€™s virtual diary archives – Thursday 18 June 2026

by . Last updated .

Word count: 1446. Estimated reading time: 7 minutes.
Summary:
The input is a combined diary entry and a highly technical guide. A dark theme implementation is described, where pure CSS variables are utilised. Automatic switching based on the operating system’s theme can be achieved using @media queries. Furthermore, a manual override is demonstrated via checkbox hacks, although significant code duplication must be accepted for the feature to be fully operational.
Thursday 18 June 2026:
07:40.
Word count: 1446. Estimated reading time: 7 minutes.
Summary:
The input is a combined diary entry and a highly technical guide. A dark theme implementation is described, where pure CSS variables are utilised. Automatic switching based on the operating system’s theme can be achieved using @media queries. Furthermore, a manual override is demonstrated via checkbox hacks, although significant code duplication must be accepted for the feature to be fully operational.
I am typing to you now from the cabin on my site where popups installation continues apace. We should be about three quarters done by end of today. It is currently raining outside but will clear later – the lads soldier on as if you didn’t you’d never get any construction in Ireland. But I have the luxury of being able to wait inside until it stops raining later today, so I’m writing this diary entry instead.

This post introduces the most radical style change to this website since the Hugo conversion back in March 2019: a dark theme which is automatically chosen by your web browser if your system’s theme is dark. If your system is configured to switch between light and dark themes based on time of day, so will this website. If you wish to override the current light-dark theme chosen, there is now a floating theme override button in the top right of the page. It works identically on mobile and desktop, and requires no Javascript: it is pure CSS only.

Major browsers gained this ability around 2020 and it works like this: Firstly, we replace all the colours in all the CSS with CSS variables, which have been available in major browsers from 2017 onwards:

/* light */
:root {
    color-scheme: light dark;

    --c-body-bg: #fff;
    --c-body-fg: #000;
    --c-navbar-bg: #fff;
    --c-navbar-divider-bg: #000;
    --c-hover-bg: rgba(0,0,0,0.1);
    --c-hover-fg: #FF0000;
    --c-hover-glow: #aa0000;
    --c-tooltip-bg: beige;
    --c-tooltip-fg: #000;
    --c-tooltip-border: chocolate;
    --c-tooltip-arrow: #000;
    --c-pre-bg: rgba(0,0,0,0.05);
    --c-quote-bg: rgba(0,0,0,0.05);
    --c-shadow: #aaa;
    --c-shadow-heavy: #000;
    --c-affiliate-border: #000;
    --c-post-details-border: #000;
    --c-link: #00e;
    --c-link-visited: rgba(85, 26, 139, 1.0);
    --c-toggle-bg: rgba(255,255,255,0.33);
    --c-toggle-fg: rgba(48,48,48,0.66);
    --c-toggle-border: rgba(48,48,48,0.33);
    --c-toggle-shadow: rgba(0,0,0,0.33);
    --c-toggle-hover-bg: #fff;
    --c-toggle-hover-fg: #333;
}

I actually had Step 3.7 Flash do most of this work, and it chose for the CSS variable names a c- prefix for reasons I don’t precisely understand, but equally it seemed a safer thing to do so I left it.

We now use the prefers-color-scheme CSS feature to tell the browser to overwrite those variables when the system theme is dark:

/* Experimental CSS5 stuff */
@media (prefers-color-scheme: dark) {
    /* dark */
    :root {
        --c-body-bg: #000;
        --c-body-fg: #fff;
        --c-navbar-bg: #000;
        --c-navbar-divider-bg: #aaa;
        --c-hover-bg: rgba(255,255,255,0.2);
        --c-hover-fg: #FF0000;
        --c-hover-glow: #ff6666;
        --c-tooltip-bg: #2d2d2d;
        --c-tooltip-fg: #d4d4d4;
        --c-tooltip-border: #555555;
        --c-tooltip-arrow: #555555;
        --c-pre-bg: rgba(255,255,255,0.2);
        --c-quote-bg:rgba(255,255,255,0.2);
        --c-shadow: #333;
        --c-shadow-heavy: #000;
        --c-affiliate-border: #444444;
        --c-post-details-border: #555555;
        --c-link: lightskyblue;
        --c-link-visited: #ad8bcd;
        --c-toggle-bg: rgba(32,32,32,0.33);
        --c-toggle-fg: rgba(240,240,240,0.66);
        --c-toggle-border: rgba(240,240,240,0.33);
        --c-toggle-shadow: rgba(255,255,255,0.33);
        --c-toggle-hover-bg: #222;
        --c-toggle-hover-fg: #eee;
    }

Finally, you add to the <head> stanza to tell the browser that you support automatic theme switching <meta name="color-scheme" content="light dark">, and you will now have automatic theme switching, where the default theme is light.

Making the theme user toggleable

Sometimes you might not want the theme to be dark or light depending on use case, so being able to override it manually is a must. You can do this trivially easy using four lines of Javascript, but I wanted to avoid Javascript so that unfortunately means stamping out quite a lot more CSS:

/* CSS-only dark mode toggle overrides via checkbox */
/* light */
#theme-toggle:not(:checked) ~ #page {
    --c-body-bg: #fff;
    --c-body-fg: #000;
    --c-navbar-bg: #fff;
    --c-navbar-divider-bg: #000;
    --c-hover-bg: rgba(0,0,0,0.1);
    --c-hover-fg: #FF0000;
    --c-hover-glow: #aa0000;
    --c-tooltip-bg: beige;
    --c-tooltip-fg: #000;
    --c-tooltip-border: chocolate;
    --c-tooltip-arrow: #000;
    --c-pre-bg: rgba(0,0,0,0.05);
    --c-quote-bg: rgba(0,0,0,0.05);
    --c-shadow: #aaa;
    --c-shadow-heavy: #000;
    --c-affiliate-border: #000;
    --c-post-details-border: #000;
    --c-link: #00e;
    --c-link-visited: rgba(85, 26, 139, 1.0);
    --c-toggle-bg: rgba(255,255,255,0.33);
    --c-toggle-fg: rgba(48,48,48,0.66);
    --c-toggle-border: rgba(48,48,48,0.33);
    --c-toggle-shadow: rgba(0,0,0,0.33);
    --c-toggle-hover-bg: #fff;
    --c-toggle-hover-fg: #333;
}

/* dark */
#theme-toggle:checked ~ #page {
    --c-body-bg: #000;
    --c-body-fg: #eee;
    --c-navbar-bg: #000;
    --c-navbar-divider-bg: #aaa;
    --c-hover-bg: rgba(255,255,255,0.2);
    --c-hover-fg: #FF0000;
    --c-hover-glow: #ff6666;
    --c-tooltip-bg: #2d2d2d;
    --c-tooltip-fg: #d4d4d4;
    --c-tooltip-border: #555555;
    --c-tooltip-arrow: #555555;
    --c-pre-bg: rgba(255,255,255,0.2);
    --c-quote-bg: rgba(255,255,255,0.2);
    --c-shadow: #333;
    --c-shadow-heavy: #000;
    --c-affiliate-border: #444444;
    --c-post-details-border: #555555;
    --c-link: lightskyblue;
    --c-link-visited: #ad8bcd;
    --c-toggle-bg: rgba(32,32,32,0.33);
    --c-toggle-fg: rgba(240,240,240,0.66);
    --c-toggle-border: rgba(240,240,240,0.33);
    --c-toggle-shadow: rgba(255,255,255,0.33);
    --c-toggle-hover-bg: #222;
    --c-toggle-hover-fg: #eee;
}

This is unfortunately very copy-and-paste, but I am unaware of doing better using pure CSS. In any case, we now overwrite those CSS variables based on whether the toggle theme checkbox is checked or not. Unfortunately we are not yet done with the copy-and-paste:

/* Experimental CSS5 stuff */
@media (prefers-color-scheme: dark) {
    /* CSS-only dark mode toggle overrides via checkbox */
    /* dark */
    #theme-toggle:not(:checked) ~ #page {
        --c-body-bg: #000;
        --c-body-fg: #eee;
        --c-navbar-bg: #000;
        --c-navbar-divider-bg: #aaa;
        --c-hover-bg: rgba(255,255,255,0.2);
        --c-hover-fg: #FF0000;
        --c-hover-glow: #ff6666;
        --c-tooltip-bg: #2d2d2d;
        --c-tooltip-fg: #d4d4d4;
        --c-tooltip-border: #555555;
        --c-tooltip-arrow: #555555;
        --c-pre-bg: rgba(255,255,255,0.2);
        --c-quote-bg: rgba(255,255,255,0.2);
        --c-shadow: #333;
        --c-shadow-heavy: #000;
        --c-affiliate-border: #444444;
        --c-post-details-border: #555555;
        --c-link: lightskyblue;
        --c-link-visited: #ad8bcd;
        --c-toggle-bg: rgba(32,32,32,0.33);
        --c-toggle-fg: rgba(240,240,240,0.66);
        --c-toggle-border: rgba(240,240,240,0.33);
        --c-toggle-shadow: rgba(255,255,255,0.33);
        --c-toggle-hover-bg: #222;
        --c-toggle-hover-fg: #eee;
    }

    /* light */
    #theme-toggle:checked ~ #page {
        --c-body-bg: #fff;
        --c-body-fg: #000;
        --c-navbar-bg: #fff;
        --c-navbar-divider-bg: #000;
        --c-hover-bg: rgba(0,0,0,0.1);
        --c-hover-fg: #FF0000;
        --c-hover-glow: #aa0000;
        --c-tooltip-bg: beige;
        --c-tooltip-fg: #000;
        --c-tooltip-border: chocolate;
        --c-tooltip-arrow: #000;
        --c-pre-bg: rgba(0,0,0,0.05);
        --c-quote-bg: rgba(0,0,0,0.05);
        --c-shadow: #aaa;
        --c-shadow-heavy: #000;
        --c-affiliate-border: #000;
        --c-post-details-border: #000;
        --c-link: #00e;
        --c-link-visited: rgba(85, 26, 139, 1.0);
        --c-toggle-bg: rgba(255,255,255,0.33);
        --c-toggle-fg: rgba(48,48,48,0.66);
        --c-toggle-border: rgba(48,48,48,0.33);
        --c-toggle-shadow: rgba(0,0,0,0.33);
        --c-toggle-hover-bg: #fff;
        --c-toggle-hover-fg: #333;
    }
}

What we are doing here is flipping the handling of the toggle theme checkbox being ticked based on the system’s current theme i.e. you get the system’s current theme on page load, if the system theme changes the website also changes its theme, and the toggle theme checkbox being ticked therefore means ‘the opposite theme of the system theme’. Next we need a theme toggle:

    <input type="checkbox" id="theme-toggle" class="theme-toggle" aria-label="Toggle dark mode">
    <div id="page">
      <label for="theme-toggle" class="theme-toggle-label" aria-label="Toggle dark mode">
        <span class="icon-moon" aria-hidden="true">๐ŸŒ™</span>
        <span class="icon-sun" aria-hidden="true">โ˜€๏ธ</span>
      </label>
      ...
    </div>

Thanks to Unicode, the sun and the moon no longer need dedicated image assets like we would do in the bad old days. Nowadays, you just use the appropriate Unicode codepoint, dead simple.

The next thing is to make our toggle theme button NOT have a visible checkbox, and to style its label so it shows the moon or the sun and that floats at the top right with a transparency so it doesn’t get in the way of reading text:

/* Visually hidden checkbox but keyboard-accessible */
.theme-toggle {
    position: fixed !important;
    top: 0;
    right: 0;
    height: 1px;
    width: 1px;
    overflow: hidden;
    clip: rect(1px, 1px, 1px, 1px);
}

/* Fixed floating dark-mode toggle button */
.theme-toggle-label {
    position: fixed;
    top: 1rem;
    right: 1rem;
    z-index: 9999;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    background: var(--c-toggle-bg);
    color: var(--c-toggle-fg);
    border: 2px solid var(--c-toggle-border);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 1.2rem;
    line-height: 1;
    box-shadow: 0px 0px 8px var(--c-toggle-shadow);
}

.theme-toggle-label:hover {
    background: var(--c-toggle-hover-bg);
    color: var(--c-toggle-hover-fg);
    border-color: var(--c-hover-fg);
}

.icon-moon,
.icon-sun {
    display: inline;
}

Finally, we need the toggle theme icon to be the correct one in all circumstances, which unfortunately requires yet more copy-and-paste CSS:

.theme-toggle:checked ~ #page .icon-moon {
    display: none;
}
.theme-toggle:not(:checked) ~ #page .icon-sun {
    display: none;
}

@media (prefers-color-scheme: dark) {
  .theme-toggle:checked ~ #page .icon-moon {
      display: inline;
  }
  .theme-toggle:not(:checked) ~ #page .icon-sun {
      display: inline;
  }
  .theme-toggle:checked ~ #page .icon-sun {
      display: none;
  }
  .theme-toggle:not(:checked) ~ #page .icon-moon {
      display: none;
  }
}

And that’s basically it – an unfortunate amount of duplication in the main CSS file, but it does get served to users gzip compressed so all that text duplication matters little to website load times. Most processors are so fast at parsing text that the extra verbiage doesn’t really matter either. As I mentioned above, you could use a few lines of Javascript and save yourself all this duplication, but I personally configure my web browser with Javascript disabled by default and I enable it on a per-site basis – only if I absolutely must use a site and it’s broken without Javascript do I enable it. If it’s a site I don’t care hugely about and it won’t load without Javascript I generally just close the tab, not worth the effort.

As mentioned above, Step 3.7 Flash did almost all of the work here. It didn’t get quite all the way there on its own though, because I wouldn’t let it install web page to image rendering via which it could debug its work. So I took it manually for the final stage of debugging and tweaking to get the last of it over the line. But I understand from reading online that if I had allowed it to visualise the results of its work and to click around the web page by itself, it should have successfully completed its task in full. The reason I didn’t give it the power to browse and interact with web pages by its itself alone is probably obvious: I hadn’t set up safety containerisation for it on my Mac, so it was effectively operating as if me.

It’ll probably be popups installation in my next diary entry. Depends on when they get it completed and what the weather will be like in the next few days. We shall see!

#website #theming




Go back to the archive index Go back to the latest entries

Contact the webmaster: Niall Douglas @ webmaster2<at symbol>nedprod.com (Last updated: 2026-06-18 07:40:06 +0000 UTC)