Adding Night Mode

The Problem:

To paraphrase colleague of mine, in 2019 anything that isn’t Night Mode should be illegal.

If you’re not familiar with the concept of Night Mode, it’s a simple change that usually just switches the background and foreground colors, usually resulting in a black or gray background with lighter text. It’s called “Night Mode” because the whole idea is to reduce eye strain if you are using the app or website in a low-light environment. As someone who uses blackboard-style themes in all the code editors I can, I can also let you know that it works great in all lighting. I don’t have quantifiable data, but anecdotally it noticeably cuts down on my own eye strain and has reduced the number of times I’ve noticed myself walking away from the computer with an eye strain related headache.

Now, I like the Brutalist simplicity of the WordPress 2019 theme, which is why I never walked away from it, but in that simplicity they opted for a dark text on a white background style, not unlike the back end of their CMS. It’s simple, clean, and a lot of people still prefer it to dark backgrounds. It’s for this reason that my colleague and I have always wanted to create a Night Mode switch for our clients and just include it standard so as to increase the general accessibility of their content. Internet Marketing is all about getting information in the hands of potential customers after all.

The Plan:

CSS variables are relatively widely adopted by most major browsers these days. If I make a block of variables at the top of the “style.css” for the twentynineteen-child theme I’m slowly putting together, we should be able to easily overwrite those variables by appending rules to the HTML content as needed.

Of course, “relatively widely adopted” is not 100% adoption, so we need to plan around some browsers not being able to understand the variables and provide them a graceful failure state.

The Method:

First things first, using my favorite Web Development editor Notepad++ I opened two copies of the stylesheet. The first I scrolled through the entirety of the existing stylesheet, looking for color definitions.

And at this point, I want to call out the WordPress team a little. I understand the CSS sheets can get unruly after a certain amount of development. I know I’ve made a pigs breakfast of enough in my (relatively short) day. But despite the lovely Table of Contents you included, I had a very hard time following the logic of some of the groupings of rules, and honestly I felt a like a lot of time and effort could have been saved if you’d gone with a property organization versus an element organization. Then again, maybe most people aren’t crazy enough to sit there and find every time you use the same #0073aa to mean an element of navigation/interaction.

For every color definition I found, I left the original rule, these would form the bulk of our fallback, since the browser will default to the most recent valid rule when it encounters a value it doesn’t understand.

/*All of my rules ended up looking like this, 
don't look for this rule in twentynineteen though, 
I added it so I could have code pre tags like you're reading now*/
pre{
     background-color: #222;
     background-color: var(--dark-bg-color);
     color: #fcfcfc;
     color: var(--dark-text-color);
 }

Additionally, I added each unique non-alpha color to a block at the top of the document. Pardon the names, I started out generic, but ended up being a little too specific just so I could keep track of them as I worked through the document.

:root {
     --main-bg-color: #fff;
     --dark-bg-color: #222;
     --text-color: #111;
     --text-hover-color: #4a4a4a;
     --dark-text-color: #fcfcfc;
     --highlight-color: #fff9c0;
     --select-color: #bfdcea;
     --link-color: #0073aa;
     --active-link-color: #005177;
     --background-gray: #767676;
     --neutral-gray: #ccc;
     --screen-reader-focus-color: #f1f1f1;
     --screen-reader-text-color: #21759b;
     --full-black: #000;
     --blue-black: #000e14;
     --user-blue: #008fd3;
     --light-gray: #ccc;
 }

The whole process took me about an hour, but it was worth it because it makes things like Night Mode, or even custom color themes a snap to develop in the future. From an accessibility standpoint, you could offer this to aid colorblind users. From a marketing standpoint, you could rotate colors around time of year if your business is seasonal. Either use case appeals enough to me that I’m strongly considering structuring my custom CSS sheets like this in the future.

Next we needed the JavaScript to actually make the switch. Luckily for me, most of the browsers that support CSS variables also support the CSS API for JavaScript and the “supports()” API function. This forms the other half of our fallback scenario. By checking if the browser supports CSS variables before we make any modifications to the page, we will never show the Night Mode button to a user who can’t use it.

The code isn’t commented, but it also isn’t complex:

if(window.CSS && CSS.supports('color', 'var(--text-color)')){
     var body = document.getElementsByTagName('body')[0];
     var nightCSS = ':root{--main-bg-color: #222;--dark-bg-color: #000;--text-color:#fff; --background-gray: #a6a6a6; --link-color: #afb4c6;}#nightmode-toggle::before{color: var(--highlight-color);}';
     var dayCSS = ':root{--main-bg-color: #fff;--dark-bg-color: #222;--text-color:#111;--background-gray: #767676;--link-color: #0073aa;}#nightmode-toggle::before{color: var(--dark-bg-color);}';
     body.innerHTML += '<style id="nightmode"></style><div id="nightmode-controls"><button title="Toggle Night-Mode" id="nightmode-toggle"></button></div>';
     var style = document.getElementById('nightmode');
     if(localStorage.getItem('nightmode') == 'true'){
         style.innerHTML = nightCSS;
     }
     document.getElementById('nightmode-toggle').addEventListener('click', function(e){
         nightmode.toggle(e);
     });
 }
 var nightmode = {
     toggle: function(e){
         if(localStorage.getItem('nightmode') == 'true'){
             style.innerHTML = dayCSS;
             localStorage.setItem('nightmode', 'false');
         }else{
             style.innerHTML = nightCSS;
             localStorage.setItem('nightmode', 'true');
         }
     }
 };

A little more style tweaks to get the controls up in the right hand corner of the page, and we’re off to the races. You can also see I elected to save and fetch the user’s state to local storage. This means the setting will persist page to page, and session to session.

You’ll also note that if you have the console open, the above code makes jQuery a little angry when trying to process the event bubbling. I haven’t yet determined where that conflict is coming from, but I wanted to stay away from jQuery for this so that the resulting code could be relatively portable.

The Results:

Click the button with the lightbulb and see for yourself. I’m happy with the results, but as the header says: nothing is ever finished. I’m sure I’ll tweak the exact color definitions. I’d like to take this a little further, letting users roll their own custom themes, adjust font sizes on the fly without having to overwrite the website’s styles, and just generally improve the idea.