I did dark mode for my blog

Damn my laziness. If you happen to run macOS Mojave or iOS 13, you might’ve noticed that I added support for dark mode quite a while back. Silly ’ol me didn’t write about it, even though I was quite happy with myself for utilizing CSS custom properties to simplify the theming. Both Chris Ferdinandi and Jeremy Keith beat me to it (or rather, their posts got me to finally write at least something about it).

Prefer dark mode

In the words of Mozilla Developer Network, the prefers-color-scheme CSS media feature is used to detect if the user has requested the system use a light or dark color theme. The spec is part of Media Queries Level 5 with a status of “Editors’s Draft” as of writing this post.

@media (prefers-color-scheme: dark) {
  /* Dark styles goes here */

I’m just glad that we’re (mostly?) past vendor prefixes at this point.

The power of CSS custom properties

To keep all things related to dark vs. light themes in one place, I leveraged the power of CSS custom properties. All colors that I use across the site is residing in a variables collection; /css/00_variables.css.

:root {
  color-scheme: light dark;

  --body-background: hsl(45, 40%, 94%);
  --body-text: hsl(0, 0%, 20%);

@media only screen and (prefers-color-scheme: dark) {
  :root {
    --body-background: hsl(0, 0%, 20%);
    --body-text: hsl(45, 40%, 94%);

This lets me keep only one instance of prefers-color-scheme: dark around, which is nice. All my other stylesheets just contain references to the custom properties.

body {
  background-color: var(--body-background);
  color: var(--body-text);

If you already utilize the cascade to its full potential, which luckily I have, you really shouldn’t need to change styles much. One such instance is the use of currentColor property value.

Thoughts on a modern CSS reset

I never was a fan of using someone elses CSS reset, like Normalize or even Eric Meyer’s reset.css, but this modern take of a CSS reset caught my eye for several reasons.

In this modern era of web development, we don’t really need a heavy-handed reset, or even a reset at all, because CSS browser compatibility issues are much less likely than they were in the old IE 6 days. That era was when resets such as normalize.css came about and saved us all heaps of hell. Those days are gone now and we can trust our browsers to behave more, so I think resets like that are probably mostly redundant.

The reset for lists is frickin’ genius!

/* Remove default padding */
ol[class] {
  padding: 0;

Why didn’t I think of that?

Extra bonus points for the piece by piece explanations for each section.

Further sticky bookmarklet fun

I just couldn’t leave that poor bookmarklet alone, could I? Using the previous version in the wild, I noticed some use cases where there was room for improvement. Right now my bookmarklet does two things:

  • It checks the <body> for any scroll disabling styling and tries to unset it
  • It finds any element with either position: fixed or position: sticky and deletes them

Sounds good enough, right? Well, not quite. I found one big glaring issue with this; headers. Many times, modern web sites use position: sticky for headers. If I delete them — well, then I’ve broken the site a little too much.

Based on my very scientific research following this, by visiting as many as ten web sites, I could conclude the following:

  1. Most annoying overlays and modals is positioned using position: fixed — these we can safely destroy with fire
  2. Most functional elements of a page, such as the header, uses position: fixed — these we want to keep, but away from scrolling view

These assumptions (dangerous as assumptions may be), lead me to modify the script accordingly.

const elements = document.querySelectorAll('body *');
const body = document.querySelector('body');

if (getComputedStyle(body).overflow === 'hidden') {
  body.style.overflow = 'unset';

elements.forEach(function (element) {
  if (['-webkit-sticky', 'sticky'].includes(getComputedStyle(element).position)) {
    element.style.position = 'unset';
  else if(['fixed'].includes(getComputedStyle(element).position)) {

I first check for position: sticky styled elements, and instead of removing them, I force the inline style to position: unset. This will reset to the initial value of position: static and so “un-sticky” any such elements on the page. Everything else that has position: fixed will instead just get deleted.

There are of course some caveats with this approach, mostly that older sites might still have sticky-ish headers that are positioned with the fixed property. I believe this is fine. Compared to the previous version, that just killed everything in sight, regardless of fixed or sticky, we now get something a bit more precise.

I’m beginning to have trouble coming up with good names for these bookmarklets, so this time around I’m just calling it “unSticky”. Besides, you’re completely free to name it whatever you wish.


Ok, that’s it for now. Let’s see how long I can leave this one alone.

Updated: Well that didn’t take long. My friend, Joacim de la Motte, decided to do some optimization for me. Apparently the IIFE is not really necessary, so I’ve removed it. The bookmarklet will still run without it.

Jeremy Keith on getting started

Jeremy Keith writes:

I got an email recently from a young person looking to get into web development. They wanted to know what languages they should start with, whether they should a Mac or a Windows PC, and what some places to learn from.

I’ve gotten the question myself on more than one occasion; how should I get into front end development? Besides my recommendation of starting out with HTML, then moving on to CSS and lastly get into JavaScript, I often had to dig deep to find good sources for people to read that would help and inspire them. Thanks to Jeremy, that job has been done for me. From now on, I’ll link to his post.

I would like to add that Jeremy’s own excellent book “Resilient Web Design” should be part of the curriculum. Best of all is that it’s available for free online.

Thanks, Keith!

Tags in JSON Feed

It seems I just can’t stop tinkering with my site. I was doing some optimizing of my deploy scripts (which now runs via Docker, because why the hell not), when I out of the blue re-read the JSON Feed spec and saw that there was an optional support for tags.

From the JSON Feed spec:

tags (optional, array of strings) can have any plain text values you want. Tags tend to be just one word, but they may be anything. Note: they are not the equivalent of Twitter hashtags. Some blogging systems and other feed formats call these categories.

An array in JSON is pretty much just this:

[ "item-1", "item-2", "item-3" ]

In context, according to the spec, each item in the items array would need the following (assuming our post has the tags “css” and “html”):

"tags": [

Getting things done in Jekyll

So this whole exercise revolves around you putting an array of tags in your front matter for each post. I’m pretty much assuming that this is something that you’ve already done, but just in case, here’s a sample post:

title: My blog post
  - css
  - html

Some thoughtful content…

Considering my previous post about getting JSON Feed in Jekyll, here’s the additional Liquid we need to get these tags in the JSON Feed code:

{% if post.tags %}
"tags": [
{% for tag in post.tags %}
  "{{ tag }}"{% if forloop.last == false %},{% endif %}
{% endfor %}
{% endif %}

Since JSON is picky with trailing commas, we need to utilize foorlop.last in order to keep tabs on whether we’re at the last item in the loop or not. Also note the trailing comma after the last bracket on the sixth row. Depending on where you put this snippet in your JSON Feed template, you may or may not need it.

Putting it all together

Here’s the full code for my own feed.json template, complete with the new section for tags.

layout: null
  priority: 0.7
  changefreq: weekly
  "version" : "https://jsonfeed.org/version/1",
  "title" : "{{ site.title }}",
  "home_page_url" : "{{ site.url }}",
  "feed_url" : "{{ "/feed.json" | absolute_url }}",
  "author" : {
    "url" : "{{ site.url }}",
    "name" : "{{ site.author }}"
  "icon" : "{{ "/apple-touch-icon.png" | absolute_url }}",
  "favicon" : "{{ "/favicon-32x32.png" | absolute_url }}",
  "items" : [
  {% for post in site.posts %}
      "title" : {{ post.title | jsonify }},
      "date_published" : "{{ post.date | date_to_xmlschema }}",
      {% if post.updated %}
      "date_modified": "{{ post.updated | date_to_xmlschema }}",
      {% else %}
      "date_modified": "{{ post.date | date_to_xmlschema }}",
      {% endif %}
      "id" : "{{ post.url | absolute_url }}",
      "url" : "{{ post.url | absolute_url }}",
      "author" : {
        "name" : "{{ site.author }}"
      "summary": {{ post.description | jsonify }},
      {% if post.tags %}
      "tags": [
      {% for tag in post.tags %}
        "{{ tag }}"{% if forloop.last == false %},{% endif %}
      {% endfor %}
      {% endif %}
      "content_text": {{ post.content | strip_html | strip_newlines | jsonify }},
      "content_html": {{ post.content | strip_newlines | jsonify }}
    }{% if forloop.last == false %},{% endif %}
  {% endfor %}

There we go! We now have tags from each post in our JSON Feed.