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:
- Choosing light → works fine.
- Choosing dark → works fine.
- Choosing system → theme applies correctly, but does not react to system changes.
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
matchMedia('(prefers-color-scheme: dark)')
gives us a live media query object.- The ‘change’ event fires whenever the system switches between dark and light.
- By listening once and calling
syncTheme()
, your site now tracks system changes automatically.
Results
With just one line of code, system mode finally behaves the way users expect:
- Users who prefer auto-switching (e.g. macOS or Windows set to Light in the day, Dark at night) will see your site follow along seamlessly.
- No extra toggles or refreshes required.
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.