317

Windows and macOS now have dark mode.

For CSS I can use:

@media (prefers-dark-interface) { 
  color: white; background: black 
}

But I am using the Stripe Elements API, which puts colors in JavaScript

For example:

const stripeElementStyles = {
  base: {
    color: COLORS.darkGrey,
    fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`,
    fontSize: '18px',
    fontSmoothing: 'antialiased',
    '::placeholder': {
      color: COLORS.midgrey
    },
    ':-webkit-autofill': {
      color: COLORS.icyWhite
    }
  }
}

How can I detect the OS's preferred color scheme in JavaScript?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • 1
    Possible duplicate of [How can I detect if Dark Mode is enabled in macOS Mojave on my website?](https://stackoverflow.com/questions/50730640/how-can-i-detect-if-dark-mode-is-enabled-in-macos-mojave-on-my-website) – mikemaccana May 31 '19 at 11:05
  • 2
    [This second answer](https://stackoverflow.com/questions/50730640/how-can-i-detect-if-dark-mode-is-enabled-in-macos-mojave-on-my-website) isn't marked as accepted, but covers how to use JavaScript with `window.matchMedia` – mikemaccana May 31 '19 at 11:07
  • 1
    I couldn't get the `@media (prefers-dark-interface)` media query to work in Chrome 80 (like you mentioned), but `@media (prefers-color-scheme: dark)` did. – Talk Nerdy To Me Mar 31 '20 at 21:08
  • Just posted a one-liner solution. Please, check it out, if you can! Thanks! – Yulian Feb 10 '23 at 09:35

11 Answers11

605
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    // dark mode
}

To watch for changes:

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
    const newColorScheme = event.matches ? "dark" : "light";
});
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Mark Szabo
  • 8,515
  • 3
  • 18
  • 26
  • 80
    I'll just leaver this here: `window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { console.log('changed!!');})` – Jaromanda X Mar 06 '20 at 03:05
  • 3
    Note that `prefers-color-scheme: dark` does not seem to work in Edge. Not in CSS or Javasript. – VDWWD Jun 07 '20 at 12:08
  • 1
    @VDWWD old Edge or new Edge (Chromium-based)? – Mark Szabo Jun 07 '20 at 13:16
  • The version I have is `Microsoft Edge 44.18362.449.0` with `Microsoft EdgeHTML 18.18363`. But I found out this is an old version. Apparently Windows Update does not automatically updates Edge... Updated manually and now it works. – VDWWD Jun 07 '20 at 14:16
  • 2
    Yes, that's the "old" version. As far as I know, the only way to detect dark mode in the EdgeHTML-based Edge is through the Windows APIs which are only available if the user "installs" the app through the Windows Store. I wouldn't bother with this as the Windows feature update 2004 (rolling out right now) replaces the "old" Egde with the new Chromium-based Edge. – Mark Szabo Jun 07 '20 at 19:41
  • This code doesn't work on Xiaomi's mobile default browser (Mi browser), but works in Chrome browser, Please help – Jafaruddeen Ansari Jun 11 '20 at 10:52
  • @JafaruddeenAnsari it seems Xioami browser does not support this standard browser API. There is nothing you can do most likely. Maybe Xiaomi has a non-standard proprietary API, but I doubt that. – Mark Szabo Jun 12 '20 at 10:05
  • Just teste with Chrome 86.0.4240.75 and Win 10 1909 and runs perfect$ – ZR87 Oct 12 '20 at 19:37
  • @MarkSzabo can i use this scenario for all browsers like safari or firefox? or i've to check another property instead `(prefers-color-scheme: dark)` for them? – Martin Rützy Nov 02 '21 at 09:35
  • I am using this in Cordova iOS App (WKWebView). works perfectly! – Rohit Singh Nov 18 '21 at 17:19
  • I am using Edge 100.0.1185.44 (Official Build) (64-bit) on windows 10 and the above does not work. The above code works fine on Chrome and Firefox. Any suggestions? – AshD Apr 21 '22 at 16:10
36

You can check the CSS Media-Queries directly with JavaScript

The window.matchMedia() method returns a MediaQueryList object representing the results of the specified CSS media query string. The value of the matchMedia() method can be any of the media features of the CSS @media rule, like min-height, min-width, orientation, etc.

To check if the Media-Query is true, the matches property can be used

// Check to see if Media-Queries are supported
if (window.matchMedia) {
  // Check if the dark-mode Media-Query matches
  if(window.matchMedia('(prefers-color-scheme: dark)').matches){
    // Dark
  } else {
    // Light
  }
} else {
  // Default (when Media-Queries are not supported)
}

To update the color-scheme dynamically based on the user's preference, the following can be used:

function setColorScheme(scheme) {
  switch(scheme){
    case 'dark':
      console.log('dark');
      
      break;
    case 'light':
      console.log('light');
      // Light
      break;
    default:
      // Default
      console.log('default');
      break;
  }
}

function getPreferredColorScheme() {
  if (window.matchMedia) {
    if(window.matchMedia('(prefers-color-scheme: dark)').matches){
      return 'dark';
    } else {
      return 'light';
    }
  }
  return 'light';
}

function updateColorScheme(){
    setColorScheme(getPreferredColorScheme());
}

if(window.matchMedia){
  var colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
  colorSchemeQuery.addEventListener('change', updateColorScheme);
}

updateColorScheme();
SanBen
  • 2,581
  • 26
  • 35
17

According to MediaQueryList - Web APIs | MDN, addListener is the correct way to listen to the change. addEventListener is not working for me on iOS 13.4.

window.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
  console.log(`changed to ${e.matches ? "dark" : "light"} mode`)
});
imgg
  • 430
  • 6
  • 12
  • 4
    Also from MDN - This is basically an alias for EventTarget.addEventListener(), for backwards compatibility purposes. – Gerrit0 Jun 21 '20 at 22:08
  • 4
    Just to quote the mentioned MDN, as I was confused and looked it up: " `addListener()` Adds to the MediaQueryList a callback which is invoked whenever the media query status—whether or not the document matches the media queries in the list—changes. This method exists primarily for backward compatibility; **if possible**, you should instead use `addEventListener()` to watch for the change event." – BananaAcid Apr 10 '21 at 03:50
  • 1
    According to MDN it's deprecated – khaki May 26 '22 at 20:58
9

Here's a one-liner based on SanBen's answer:

const getPreferredScheme = () => window?.matchMedia?.('(prefers-color-scheme:dark)')?.matches ? 'dark' : 'light';
Yulian
  • 6,262
  • 10
  • 65
  • 92
5

Before typing your JavaScript:

You can use CSS to make a @media query that says to have an ::after pseudo class on the body element that has a different text depending on the color-scheme of the user. In order to make sure the ::after on the body element doesn't confuse users, add a display: none; on the after element.

CSS Code:

@media (prefers-color-scheme:dark){
    body::after{
        content: 'd';
        display: none;
    }
}
@media (prefers-color-scheme:light){
    body::after{
        content: 'l';
        display: none;
    }
}

Now for your JavaScript:

Since we have an object in the document to select, we can get the ::after pseudo class of the body element. We need to get the content of it, just make sure your CSS loads before your JavaScript does! 'd' for dark mode, and 'l' for light mode.

JavaScript Code:

var colorScheme = getComputedStyle(document.body,':after').content;
// d for dark mode, l for light mode.

Why this could be useful

You can do this in CSS and HTML, but why would you do it in JavaScript?

You would use JavaScript because you might have to add an img element, but the image has text, so you got 2 images, one for light mode, the other for dark mode. So, you could use JavaScript to change the src attribute's value of the img element to the correct URL based off of the color-scheme.

There is probably more uses, but that is the use I can think of.

Sources:

I learned getComputedStyle function from this stackoverflow article.

I learned @media (prefers-color-scheme:color scheme to detect) from MDN Web Docs.

I learned how to get .content of computed style from seeing it as a code suggestion on VSCode as I typed getComputedStyle(document.body,':after') and it working as I expected it to. (If I saw it on an article, I can't find which I visited)

Tworsfeline
  • 61
  • 1
  • 3
3

Check the matchMedia option:

function getTheme() {
  if(window.matchMedia && window.matchMedia("(prefers-color-scheme:dark)").matches) {
    return "dark";
  } else {
    return "light";
  }
}
ItzCrasher
  • 3
  • 1
  • 2
ArekTOK
  • 31
  • 1
  • 1
    Thanks for your contribution - unfortunately this doesn't look like valid JavaScript. If you want to fix it and edit your answer though I'll upvote it! – mikemaccana Sep 27 '22 at 11:48
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 30 '22 at 09:00
2

Using optional chaining on matchMedia:

const theme = window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light"
Zel
  • 131
  • 3
  • 4
  • 1
    Nice use of optional chaining. Since this only has two results you could make it a boolean called `isDark` for tighter code. – mikemaccana Jul 13 '23 at 13:25
0

for people using react, see the useDarkMode hook. Which listens for changes and also allows you to toggle/change the dark mode.

import { useDarkMode } from 'usehooks-ts'

export default function Component() {
  const { isDarkMode, toggle, enable, disable } = useDarkMode()
  return (
    <div>
      <p>Current theme: {isDarkMode ? 'dark' : 'light'}</p>
      <button onClick={toggle}>Toggle</button>
      <button onClick={enable}>Enable</button>
      <button onClick={disable}>Disable</button>
    </div>
  )
}
Flion
  • 10,468
  • 13
  • 48
  • 68
0

If you are using Bootstrap 5 and you want to default to dark if JavaScript is off use this:

// don't hurt peoples eyes /////////////////////////////////////////////////////
const matchPrefersLight = window.matchMedia('(prefers-color-scheme:light)');
if (matchPrefersLight.matches) {
  document.documentElement.setAttribute('data-bs-theme', 'light');
}
matchPrefersLight.addEventListener('change', event => {
  document.documentElement.setAttribute('data-bs-theme', event.matches ? "light" : "dark");
});

And also use your top element:

<html lang="en-US" data-bs-theme="dark">

or whatever other language you are using.

William Entriken
  • 37,208
  • 23
  • 149
  • 195
0

Here's a 1k script that adds appearance-changed event to detect OS theme changes.

// fires every time the OS theme changes
window.addEventListener('appearance-changed', function(e) {
  console.log(e.detail); // `light`, `dark`
});

Also adds window.appearance which you can use to get the current OS theme:

switch (window.appearance) {
    
    case 'light': {
       // do some light theme stuff
    } break;

    case 'dark': {
       // do some dark theme stuff
    } break;
}

It's open source on GitHub.

John Doherty
  • 3,669
  • 36
  • 38
-4

You need color-mode If you develop your app using nuxt.

Aborn Jiang
  • 981
  • 10
  • 9