Adding a night mode to your web app in pure CSS and JavaScript
Tags: css, javascript, nightmode
Published: 2020-03-15 22:27:27

Adding a night-mode to your web pages is something that is ever-increasing in popularity, and, like everything else, should be done as simply and as neatly as possible, which is what I would certainly like to think I have achieved here.

What should a night mode system contain?
- A darker theme for night-reading compared to the day theme (if your website is naturally dark anyway, maybe you don't need this at all?)
- A way for the user to independently toggle between day and night modes - regardless of what your website thinks it should be doing
- (Optional) A way to auto-detect whether it is day or night for the user

How does this method work? Simply by adding and removing the 'night' class from our html element as appropriate and letting CSS handle the rest.

We'll begin with a declaration to make it easier to select our html element (select body instead if you wish - in fact, if you're using it on CodePen, definitely do it) and our nightButton - the button we will use to toggle between day and night modes.

```javascript
const keyElems = {
  html        : document.querySelector('html'),
  nightButton : document.querySelector('.night-button')
};
```

This way, rather than writing out the querySelector every time we want to select the html or the nightButton, we simply write `keyElems.html`, easy. Feel free to add other elements to this object as well, perhaps your title, a page heading, a background image, ¡lo que sea!
Next we need a way to find out if our page is already in night mode, so if the user toggles it we then know what to do:

```javascript
const isNight = () => keyElems.html.classList.contains('night');
```

`isNight()` will now either return true or false. Now, for the meat of the system, the function to set night mode (or day mode as the case may be):

```javascript
function setNight(night) { // Give the function an argument of either true or false
  if (night) { // If it's true, set it to night mode
    keyElems.html.classList.add('night');
    keyElems.nightButton.textContent = `Day Mode`; // Change the button to now say Day Mode
  } else {
    keyElems.html.classList.remove('night');
    keyElems.nightButton.textContent = `Night Mode`;
  }
}
```

Last but not least for the non-optional parts is a way to toggle the night mode, which we'll also add to our night mode button when it gets clicked:

```javascript
function toggleNight() {
  setNight(!isNight()); // Easy as pie, toggling something is simply setting it to be the opposite of what it currently is.
}
```

Now, if you want to add a feature that auto-detects whether it is night for the user, there are also a number of ways you could do this. I have gone for checking the time and whether it's between 7PM and 7AM as it's probably the most simple. Keep in mind that when working with time on a user's browser, it's always their time, so if your server is in the UK and your user is in Brazil, Egypt or Australia, it doesn't matter, it will always run on their time, not yours.

There are a couple of other ways you could it though:
- Using the user's location (**permissions needed**), pull the sunrise and sunset times for, let's say London, from an API and set the night mode based on that
- Using the user's light sensor on their smartphone (**permissions *definitely* needed**, imagine a website asking to use your light sensor, or even front camera) detect whether it is dark where they are. It could be midday in the desert but they might be in a dark room.

```javascript
function checkNightOnLoad() {
  let h = new Date().getHours();
  if (h < 7 || h > 18) {
    setNight(true);
  } else {
    setNight(false);
  }
}
```

Now to make all this work when the page actually loads:

```javascript
document.addEventListener('DOMContentLoaded',() => {
  checkNightOnLoad(); 
  keyElems.nightButton.addEventListener('click',toggleNight);
});
```

Leaving us with a full JavaScript file that looks like this:
```javascript
const keyElems = {
  html        : document.querySelector('html'),
  nightButton : document.querySelector('.night-button')
};

const isNight = () => keyElems.html.classList.contains('night');

function setNight(night) {
  if (night) {
    keyElems.html.classList.add('night');
    keyElems.nightButton.textContent = `Day Mode`;
  } else {
    keyElems.html.classList.remove('night');
    keyElems.nightButton.textContent = `Night Mode`;
  }
}

function toggleNight() {
  setNight(!isNight());
}

function checkNightOnLoad() {
  let h = new Date().getHours();
  if (h < 7 || h > 18) {
    setNight(true);
  } else {
    setNight(false);
  }
}

document.addEventListener('DOMContentLoaded',() => {
  checkNightOnLoad(); 
  keyElems.nightButton.addEventListener('click',toggleNight);
});
```

Now moving on to our CSS, frankly this is possibly the easy bit!

Let's say we have our fairly simple CSS file that currently looks like this:

```css
html {
  padding: 0;
  color: black;
  background-color: white;
}
.night-button {
  position: fixed;
  bottom: 0;
  right: 0;
  font-weight: bold;
  background-color: black;
  color: white;
  border: none;
  padding: 1rem;
  border-top-left-radius: 1rem;
}
```

What can we then add to this to get our night mode styles visible? As it is if we click the button then the text will change, and in the DOM the html element will have the night class added and removed from it, but nothing else will happen. And the whole point of this exercise is to make it look different at night!

Luckily the fix is fairly simple:

```css
html {
  padding: 0;
  color: black;
  background-color: white;
}
.night-button {
  position: fixed;
  bottom: 0;
  right: 0;
  font-weight: bold;
  border: none;
  padding: 1rem;
  border-top-left-radius: 1rem;
  background-color: black;
  color: white;
}
html.night {
  color: white;
  background-color: black;
}
.night .night-button {
  color: black;
  background-color: white;
}
// Here is where you add anything else you want to look different at night
.night img {
  filter: grayscale(100%);
} // I think greyscaling images is pretty cool in night mode, but you can literally do what you want!
```

It becomes even easier in a preprocessor:
```scss
html {
  padding: 0;
  color: black;
  background-color: white;
  &.night {
    color: white;
    background-color: black;
  }
}
.night-button {
  position: fixed;
  bottom: 0;
  right: 0;
  font-weight: bold;
  border: none;
  padding: 1rem;
  border-top-left-radius: 1rem;
  background-color: black;
  color: white;
}
.night {
  .night-button {
    color: black;
    background-color: white;
  }
  img {
    filter: grayscale(100%);
  }
}
```

See it in action on my [CodePen](https://codepen.io/gavinsykesuk/full/VwYEzXQ) - this is the first pen that I added it to but I will be adding it to frankly all of them going forwards!

As always, all thoughts and comments on this are welcome below!