zy1p's blog

How to Sync Website Theme with System Dark/Light Mode

2 min read

Fix the common issue where “system” theme does not auto-update. Learn how to use matchMedia to sync your site with system dark/light changes in real time.

Contents

Background

Almost every modern site has a dark mode toggle, and libraries like shadcn-ui already give us a neat ModeToggle component.
It works great for switching between light, dark, and system preferences.

However, there’s a subtle issue:
When you select system, it will correctly apply your system’s theme at that moment. But if you later change your system’s appearance (for example, switching macOS from Light to Dark), the website won’t update automatically. You’d need to toggle again manually to sync the theme.

The Problem

Here’s what happens when you actually use the toggle:

This breaks the expectation that “system” mode should always reflect the system’s current theme.

The Fix

The solution is to listen to system appearance changes using the prefers-color-scheme media query.
When the system switches between dark and light, trigger your theme sync function.

window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => syncTheme())

Here, syncTheme() is whatever function you’re using to apply or recalc the theme (in many setups, it toggles the dark class on <html>).

Why This Works

Results

With just one line of code, system mode finally behaves the way users expect:

Conclusion

Adding a matchMedia listener turns “system” mode from a one-time check into a live feature that always follows the OS. It closes the gap where users expect your site to adapt automatically, but it doesn’t.

It’s a small change for us as developers, but it makes a big difference in how polished the site feels. No extra toggles, no refreshes — just a theme that respects the user’s system preference in real time.

These are the little details most people never notice when they work, but they immediately notice when they don’t.