Skip to contentVisit my new website at chrisnicholas.dev
Article posted on

Dark Mode by Local Sunlight
Automatically switch themes in sync with the user's solar day

Dark mode is increasingly appearing as an option on websites, but why not have it enable automatically? We can estimate the sunlight levels at a user's location, and apply the correct theme to soothe their eyes.
Table of contents

Dark mode is increasingly appearing as an option on websites, but why not have it enable automatically? We can estimate the sunlight levels at a user's location, and apply the correct theme to soothe their eyes.

How do we do this?

The first step is finding the users' locations; the sun doesn't set everywhere at the same time, and other solar phases also differ depending on your location on the planet. For example, a user in Iceland could be set to dark mode at the same (local) time as a user in South Africa will be set to light:

Light mode
14:00 on 1 Jan, Reykjavik
Light mode
14:00 on 1 Jan, Johannesburg

Finding your users

There are multiple ways to find your users' locations, but for this example we'll stick with the Geolocation API, and retrieve their latitude & longitude from its coordinates object:

navigator.geolocation.getCurrentPosition(position => {
  const { latitude, longitude } = position.coords
})

The Geolocation API requires users to accept permission in a browser dialog box, but if we don't want this we can find locations using a backend API.

Getting the time

How do we figure out the time of local sunsets & sunrises? We can't just use location, because the time of year affects solar phases too. If you play with the example below, you can see there's around 5 hours of variance between sunset in January and June in Ireland.

Light mode
10:00 on 1 Jan, Dublin
Theme timetable
Theme modeTime period
Dark12:00:00 AM - 7:51:32 AM
Light7:51:32 AM - 3:27:20 PM
Dark3:27:20 PM - 12:00:00 AM
Time of dayTime of year

Even in equatorial nations, whilst the amount of sunlight barely changes across seasons, the time of the sunset still varies by around an hour, thanks to the elliptical nature of the earth's orbit. You can see this in Quito, Ecuador, one of the most longitudinally medial capital cities:

Light mode
10:00 on 1 Jan, Quito
Theme timetable
Theme modeTime period
Dark12:00:00 AM - 6:14:32 AM
Light6:14:32 AM - 6:22:23 PM
Dark6:22:23 PM - 12:00:00 AM
Time of dayTime of year

Getting local solar times

There's a handy NPM package named SunCalc (created by @mourner) that we can use to find the local solar times.

npm install suncalc

suncalc.getTimes() takes three arguments, (date, latitude, longitude) and returns an object with a number of date properties attached. We'll be using sunrise and sunset (the full syntax and return values can be found on GitHub).

import SunCalc from 'suncalc'

const date = new Date('January 1, 2022 12:00:00')
const { sunrise, sunset } = SunCalc.getTimes(date, 10, -10)

// Sat Jan 01 2022 06:58:07
console.log(sunrise)

// Sat Jan 01 2022 18:31:08
console.log(sunset)

We can use getTimes() in conjunction with the geolocation snippet from earlier and then compare with the users' current time to check if it's currently before or after sunset.

import SunCalc from 'suncalc'

navigator.geolocation.getCurrentPosition(position => {
  const { latitude, longitude } = position.coords
  const now = new Date()

  // Get sun phase times for today, in the current user's location
  const { sunrise, sunset } = SunCalc.getTimes(now, longitude, latitude)

  // Compare sunrise and sunset with current time
  if (now < sunrise || now > sunset) {
    // Dark mode: Before sunrise, or after sunset
    ...
  } else {
    // Light mode: Any other time
    ...
  }
})

As simple as that! Here's a live demo that retrieves your current mode:

??? mode
At ??? in your location
Theme timetable

Adding Twilight mode

We could even add more themes, such as a twilight theme, to be displayed before and after sunrise/sunset:

Light mode
10:00 on 1 Jan, Helsinki
Theme timetable
Theme modeTime period
Dark12:00:00 AM - 9:28:14 AM
Twilight9:28:14 AM - 12:28:26 PM
Light12:28:26 PM - 2:20:56 PM
Twilight2:20:56 PM - 5:21:09 PM
Dark5:21:09 PM - 12:00:00 AM
Time of dayTime of year

Technically speaking, twilight is the period of time between either dawn & sunrise, or sunset & dusk, however real twilight doesn't last too long, so I'm going to include golden hour as part of twilight mode:

Midnight
Midday
Dawn
Twilight
Sunrise
Golden hour end
Golden hour
Twilight
Sunset
Dusk

Twilight mode will now be enabled for around twice the length of before, and will be enabled just either side of sunrise and sunset. Using SunCalc, it looks like this:

const { dawn, dusk, goldenHourEnd, goldenHour } = SunCalc.getTimes(now, longitude, latitude)

if (now < dawn || now > dusk) {
  // Dark mode
} else if (now < goldenHourEnd || now > goldenHour) {
  // Twilight mode
} else {
  // Light mode
}

Test your current location:

??? mode
At ??? in your location
Theme timetable

Accessibility first

Automatically setting the theme is a nice touch for most, but accessibility comes first, and if the user has a preference, it's always right to respect their choice. A number of people suffer from astigmatism and light text on a dark background can worsen a visual issue called halation, which looks something like this:

Dark mode
At 03:20 on 23 May in São Paulo
Theme timetable
Dark mode
At 03:20 on 23 May in São Paulo
Theme timetable
Dark mode
At 03:20 on 23 May in São Paulo
Theme timetable
Dark mode
At 03:20 on 23 May in São Paulo
Theme timetable
Astigmatic halation
No astigmatism

You can check for users' colour scheme preferences in CSS & JavaScript with the following snippets:

@media (prefers-color-scheme: dark) {
    /* Dark mode preference */
}
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    // Dark mode preference
}

Automatically selecting dark mode by local sunlight is quite simple, but remember—if you have a dark mode, it's good practice to include a switch.