How can I detect if a user’s operating system prefers dark mode and change your site using CSS and JS

The original link: www.hanselman.com/blog/how-to…

I have receivedStevo, JohnHe said he found the existing light themes of my blog jarring when he was living in dark mode. I never really thought about it before, but once he said it, it was obvious. Not only should I support dark mode, BUT I should also detect user preferences and switch seamlessly. If the browser or operating system also changes, I should also support changing modes. Stevo was kind enough to send in some sample CSS and some links, so I started exploring the topic.

Consider the following when using preferred color schemes and detecting dark patterns:

  • Use existing themes whenever possible.
    • I don’t want to have style.css and style-dark.css if I can avoid it. Otherwise it would be a maintenance nightmare.
  • Make it work for all of my sites
    • Dear reader, I have three logical sites that look like two sites to you. I have hanselman.com, hanselman.com/blog and hanselminutes.com. They do share some CSS rules, but they are written in a different substyle of ASP.NET
  • Consider the third side widget
    • I use grammar highlights for my blog (very, very old) and my podcast uses Simplecast’s podcast HTML5 player. I don’t like dark MODE, and then there’s a big old LIGHT MODE podcast player that scares people off. Therefore, I need context throughout.
  • Consider the initial state of the page and changes to the stage.
    • Sure, I can make the page look good as you load it, and if you change the mode (from dark to light and back) as you view the page, it should change, too, right? Consider all of the above.

You can set Chrome/Edge browser to use system Settings, light or dark colors. Search for topics in Settings.

All this, I can only do during my lunch hour, because this blog is not really my day job. Let’s go!

Preferred color scheme CSS Media query

I love CSS@media queries and have been using them to support mobile and tablet devices for years. Today, they are a staple of responsive design. Turns out you can just use @media queries to see if users prefer dark mode.

@media (prefers-color-scheme: dark) {
Copy the code

Sweet. Anything here (the C in CSS stands for cascading, remember) will override what came before. Here are some of the starting rules I changed. I just make changes in the F12 tool inspector and collect them back to my main CSS page. If you are an organized CSS person with a design system, you can also use variables.

These are just a few, but you get the idea. Note the.line-tan example, where I also say “just restore it to its original value.” This is usually much easier than coming up with the “opposite” value, which in this case means generating some PNG.

@media (prefers-color-scheme: dark) {
    body {
        color: #b0b0b0;
        background-color: #101010;
    }
 
    .containerOuter {
        background-color: #000;
        color: #b0b0b0;
    }
 
    .blogBodyContainer {
        background-color: #101010;
    }
 
    .line-tan {
        background: initial;
    }
 
    #mainContent {
        background-color: #000;
    }
...snip...
}
Copy the code

Sweet. This change to my main CSS applies to the hanselman.com main site. Let’s do a blog now that includes a 3rd grammar highlighter. I use the same basic rules as my main site, but also had to (sorry CSS guys) get radical and over-important with this very old syntax highlighter, like this:

@media (prefers-color-scheme: dark) { .syntaxhighlighter { background-color: #000 ! important } .syntaxhighlighter .line.alt1 { background-color: #000 ! important } .syntaxhighlighter .line.alt2 { background-color: #000 ! important } .syntaxhighlighter .line { background-color: #000 ! important } ... snip... }Copy the code

Your mileage may vary, but it all depends on the tool. If not! Important, I would not be able to finish the job. I was told it was not welcome. I’m sorry.

Use JavaScript to detect dark mode preferences

The third party control I use for podcasting, like many controls, is an iFrame. Therefore, it requires some parameters as URL query string parameters.

I generate the iFrame like this:

<iframe id='simpleCastPlayeriFrame'
title='Hanselminutes Podcast Player'
frameborder='0' height='200px' scrolling='no'
seamless src='https://player.simplecast.com/{sharingId}'
width='100%'></iframe>
Copy the code

If I add “dark=true” to the query string, I get different player skins. This is just an example, but typically 3rd side integration requires queryString or variables or custom CSS. You need to work with your vendor to make sure they care about more than just dark mode (thanks Simplecast!). And they have a way to easily enable it like this.

But this raises some interesting questions. I need to use JavaScript to detect preferences and make sure the correct player is loaded.

I also want to notice if the theme changes (from light to dark or back) and dynamically changes my CSS (this part happens automatically by the browser) and this player (which has to be done manually because dark mode is invoked through the URL) query string segments.

This is my code. Again, not a JavaScript expert, but this comes naturally to me. If it’s not super idiomatic or it’s just terrible, please email me and I’ll update it. I do check window.Matchmedia at least without feeling scared when an old browser comes up.

if (window.matchMedia) { var match = window.matchMedia('(prefers-color-scheme: dark)') toggleDarkMode(match.matches); match.addEventListener('change', e => { toggleDarkMode(match.matches); }) function toggleDarkMode(state) { let simpleCastPlayer = new URL(document.querySelector("#simpleCastPlayeriFrame").src); simpleCastPlayer.searchParams.set("dark", state); document.querySelector("#simpleCastPlayeriFrame").src = simpleCastPlayer.href; }}Copy the code

ToggleDarkMode is a method, so I can use it for the initial and “changed” states. It uses URL objects because it is too late to parse strings. I set searchParams instead of.append because I know it’s always set. I set up

When I wrote this article, I thought I could store Document.querySelector () like matchMedia, but I’m just seeing it now. Damn it. Still, it works! So I #shipit.

I’m sure I missed a page or two or an element or three, so if you find white pages or errors, please submit them here at github.com/shanselman/… I’ll take a look at it as soon as I can.

All in all, an interesting lunch time. Thanks Stevo for pushing!

Now, dear reader, you can update your site for light and dark modes.

Sponsor: The number one reason developers choose Couchbase? You can easily query and access JSON using your existing SQL++ skills. More strength and flexibility with less training. Learn more.

About Scott

Scott Hanselman is a former professor, former chief architect of finance, speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comedian as well as a writer and writer.