How to detect dark mode using JavaScript
By Flavio Copes
Learn how to detect dark mode in JavaScript with window.matchMedia and the prefers-color-scheme query, and listen for change events when the mode switches.
To detect dark mode in JavaScript we use window.matchMedia() with the prefers-color-scheme media query. If the query matches, the user has dark mode enabled at the operating system level.
Using CSS we can detect dark mode with the same prefers-color-scheme media query.
But.. what if we have to use JavaScript? I recently stumbled on this problem, because I had some JavaScript code that added an image to the page, but I wanted to show a different image based on the light/dark mode.
Here’s how we can do it.
Checking the current mode
window.matchMedia() takes a media query string, the same kind you’d write in CSS, and returns an object with a matches property.
First, detect if the matchMedia object exists (otherwise the browser does not support dark mode, and you can fall back to light mode).
Then, check if it’s dark mode using
window.matchMedia('(prefers-color-scheme: dark)').matches
This will return true if dark mode is enabled.
Here’s a full example, where I invert the colors of an image if it’s dark mode:
const img = document.querySelector('#myimage')
if (window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches) {
img.style.filter = 'invert(100%)'
}
Notice that this reflects the system preference. If your site has its own light/dark toggle stored in a cookie or in localStorage, this query knows nothing about it. In that case, check your own stored value instead.
Reacting when the mode changes
There is a problem though: what if the user changes mode while using our website?
macOS for example can switch from light to dark automatically at sunset. The code above runs once, so the image would stay in the wrong state.
We can detect the mode change using an event listener, like this:
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', event => {
if (event.matches) {
//dark mode
} else {
//light mode
}
})
The change event fires every time the preference flips, and event.matches tells you the new state.
A pitfall with older Safari
Be careful with addEventListener on the object returned by matchMedia(). Safari only added support for it in version 14. Older versions of Safari expose a legacy addListener() method instead.
If you need to support those browsers, you can fall back to the old method:
const query = window.matchMedia('(prefers-color-scheme: dark)')
if (query.addEventListener) {
query.addEventListener('change', handleChange)
} else {
query.addListener(handleChange)
}
For modern browsers, addEventListener is all you need.
Related posts about js: