DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

How I Built a Dark Mode Firefox Extension That Schedules by Sunset: NightOwl Technical Walkthrough

I wanted a dark mode extension that automatically scheduled itself by sunset and sunrise. Dark Reader (the dominant dark mode extension) does not have this feature. So I built NightOwl.

This is the technical approach I used for the Firefox WebExtension.


The core problem: dark mode that follows the sun

The behavior I wanted: dark mode turns on at sunset, off at sunrise, without any manual toggle, correctly scheduled for the user's location.

This required solving three sub-problems:

  1. Getting the user's location
  2. Computing sunset/sunrise times from coordinates
  3. Applying a CSS transformation to every page

Location: the Geolocation API

Firefox WebExtensions can call navigator.geolocation.getCurrentPosition() from a background script. I request permission once and cache the coordinates. I do not need precise location — the accuracy of city-level GPS is sufficient for sunset/sunrise within a few minutes.

navigator.geolocation.getCurrentPosition(
  (position) => {
    const { latitude, longitude } = position.coords;
    browser.storage.local.set({ lat: latitude, lon: longitude });
  },
  (error) => {
    // Fall back to manual time schedule if geolocation denied
  }
);
Enter fullscreen mode Exit fullscreen mode

The user can deny geolocation permission and fall back to a manual schedule (e.g., "dark mode from 8pm to 6am").


Sunset/sunrise calculation: the astronomy formula

I did not want an external API call for sunset times — that would add a network dependency and expose location data to a third party.

The sunset/sunrise calculation can be done in pure JavaScript using the NOAA solar algorithm. The formula takes latitude, longitude, and date, and returns sunrise and sunset in local time.

The key variables in the calculation are:

  • Julian Day Number (days since January 1, 4713 BC)
  • Hour angle at sunrise/sunset (derived from solar zenith angle)
  • Equation of time (correction for the eccentricity of Earth's orbit)
  • Solar declination (angle between sun and equator)

The full calculation is about 50 lines of math. The result is accurate to within a few minutes for most latitudes.


CSS injection: the content script

The dark mode transformation is applied by a content script that runs on every page. The approach I use is a CSS filter on the root element combined with an inverted filter on images to restore their natural colors:

html {
  filter: invert(100%) hue-rotate(180deg);
}

img, video, iframe, canvas, picture {
  filter: invert(100%) hue-rotate(180deg);
}
Enter fullscreen mode Exit fullscreen mode

This is the "invert" approach — it inverts all colors (dark backgrounds become dark themes), then rotates the hue to restore natural color relationships. Images are double-inverted to appear normal.

The limitation: some sites with complex CSS or custom canvas rendering do not look right with this approach. For those, users can add them to an exclusion list.


Scheduling: alarms API

Firefox WebExtensions have an alarms API for scheduling callbacks. I set two alarms each day: one at sunrise and one at sunset. When an alarm fires, the background script toggles dark mode and reinjects the CSS.

browser.alarms.create('sunset', { when: sunsetTime });
browser.alarms.create('sunrise', { when: sunriseTime });

browser.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'sunset') enableDarkMode();
  if (alarm.name === 'sunrise') disableDarkMode();
});
Enter fullscreen mode Exit fullscreen mode

The result

NightOwl is about 300 lines of JavaScript: 50 lines for sunset math, 100 for the background scheduling logic, 100 for the content script, 50 for settings UI.

NightOwl — Firefox Add-ons


NightOwl is one of 11 Firefox extensions I built — see the full series.

Top comments (0)