happymode: a tiny macOS menu bar app that keeps Dark Mode on schedule

/ Kamran Tahir / 5 min read READ
happymode: a macOS menu bar app that switches Light/Dark based on sunrise/sunset or custom times

Why Not Just Use macOS “Auto” (or NightOwl)?

I tried.

On a couple of my machines, macOS’ built-in Auto appearance switching just… wasn’t reliable. Sometimes it wouldn’t flip when it should, and sometimes it felt like it “worked until it didn’t”.

I also tried NightOwl. It’s a great app, but for what I wanted it felt:

  • a bit inconsistent (for my setup), and
  • a bit too configurable (lots of knobs for a problem I wanted to be boring).

So I built something intentionally simple:

  • no global shortcuts / hotkeys
  • no automations beyond “switch at the right time”
  • no external APIs

Why I Built happymode

I wanted my Mac to switch between Light and Dark automatically — and I wanted it to feel “native”, not like a background daemon with a dozen knobs.

My personal wishlist was pretty small:

  • No external services (no “weather” API calls, no tracking, no accounts).
  • Predictable schedules (either solar-based or exact times I pick).
  • Permission-aware UX (macOS can be strict about Location + Automation).
  • Menu bar-first (fast, quiet, and out of the way).

That became happymode: a menu bar app that switches appearance using sunrise/sunset (with local math) or custom daily times.

The loop is intentionally simple: get a coordinate (or use manual), compute the next transition, and flip appearance when the clock crosses it.

Architecture at a Glance

Architecture diagram: location -> solar/custom schedule engine -> apply appearance -> menu bar + settings UI

At a high level:

  • Menu bar UI (left-click popover + right-click context menu)
  • Settings window with dedicated panes (General, Schedule, Location, Permissions, About)
  • ThemeController that:
    • resolves location (or uses manual coordinates)
    • computes sunrise/sunset or custom time transitions
    • updates a countdown / “next switch” label
    • applies the appearance change

Scheduling: Sunrise/Sunset Without Any API Calls

Solar schedules sound “simple” until you hit edge cases:

  • The user denies location.
  • The user is traveling (time zone changes).
  • You’re in regions with polar night (no sunrise) or midnight sun (no sunset).

happymode does the sunrise/sunset math locally. If sunrise or sunset is missing for a day, it classifies the day as always-dark or always-light and shows that in the UI instead of failing silently.

Here’s the shape of that logic (simplified):

enum SolarDayType {
  case normal(sunrise: Date, sunset: Date)
  case alwaysDark
  case alwaysLight
}

Small modeling tip

If you’re building anything time-based: model “no event exists” explicitly. It keeps your scheduling logic honest, especially in weird calendar/latitude situations.

Scheduling: Custom Daily Times (Light at X, Dark at Y)

Solar mode is great for “set and forget”, but sometimes you want “Dark at 7pm, always”.

Custom scheduling is built around two daily transition times:

  • a Light time
  • a Dark time

The scheduler finds:

  1. the most recent past event (that tells the current state), and
  2. the next upcoming event (that tells the next transition).

The important part is handling cases like:

  • Light time is after Dark time (crosses midnight)
  • both events happen on the same day
  • user accidentally sets identical times (invalid schedule)

Actually Switching macOS Appearance (and Permissions)

On macOS, changing system appearance programmatically goes through System Events. That means happymode needs Automation permission.

When permission is missing, happymode doesn’t pretend everything is fine — it shows a clear warning and points you to the right System Settings screen.

The mechanism is intentionally boring: an AppleScript command that toggles dark mode:

tell application "System Events"
  tell appearance preferences
    set dark mode to true
  end tell
end tell

Automation permission is required to flip appearance. Location permission is only required for automatic sunrise/sunset by detected coordinates (you can still use manual coordinates or custom times without it).

UI: Menu Bar First, Settings When You Need Them

happymode menu bar popover showing Auto/Light/Dark and next switch countdown

The popover is the main control surface:

  • switch between Auto, Light, and Dark
  • in Auto mode, pick Sunrise/Sunset or Custom Times
  • see what happens next (countdown + today’s times)

Settings: General, Schedule, Permissions

happymode general settingshappymode schedule settings showing weekly previewhappymode permissions settings for Location and Automation

Updates: Simple, Transparent, and Rate-Limited

Auto-update frameworks are great, but for this app I kept update checks simple:

  • check GitHub Releases for the latest tag
  • rate-limit automatic checks to once per day
  • allow a manual “Check for Updates…” action

That’s enough for a small utility — and it keeps the entire update story visible and debuggable.

Install and Source

  • Homebrew: brew tap happytoolin/happytap then brew install --cask happymode
  • Source: happytoolin/happymode on GitHub

Not notarized (yet)

happymode is currently not notarized. On first launch, macOS Gatekeeper may block it. If that happens, use System Settings → Privacy & Security → Open Anyway, or remove the quarantine attribute:

xattr -dr com.apple.quarantine "/Applications/happymode.app"

If you build macOS utilities for yourself, I highly recommend the menu bar route: you get fast interactions, minimal surface area, and a UX that feels at-home on macOS.