How to override css prefers-color-scheme setting

JimmyBanks picture JimmyBanks · May 25, 2019 · Viewed 16.7k times · Source

I am implementing a dark mode, as macOS, Windows and iOS have all introduced dark modes.

There is a native option for Safari, Chrome, and Firefox, using the following CSS media rule:

@media (prefers-color-scheme: dark) {
body {
    color:#fff;
    background:#333333
}

This will automatically identify systems that are set to dark modes, and apply the enclosed css rules.

However; even though users may have their system set to dark mode, it may be the case that they prefer the light or default theme of a specific website. There is also the case of Microsoft Edge users which does not (yet) support @media (prefers-color-scheme. For the best user experience, I want to ensure that these users can toggle between dark and default modes for those cases.

Is there a method that this can be performed, possibly with HTML 5 or JavaScript? I'd include the code I have tried, but I haven't been able to find any information on implementing this whatsoever!

Answer

JimmyBanks picture JimmyBanks · Jun 11, 2019

I have determined an appropriate solution, it is as follows:

CSS will use variables and themes:

// root/default variables
:root {
    --font-color: #000;
    --link-color:#1C75B9;
    --link-white-color:#fff;
    --bg-color: rgb(243,243,243);
}
//dark theme
[data-theme="dark"] {
    --font-color: #c1bfbd;
    --link-color:#0a86da;
    --link-white-color:#c1bfbd;
    --bg-color: #333;
}

The variables are then called where necessary, for example:

//the redundancy is for backwards compatibility with browsers that do not support CSS variables.
body
{
    color:#000;
    color:var(--font-color);
    background:rgb(243,243,243);
    background:var(--bg-color);
}

JavaScript is used to identify which theme the user has set, or if they have over-ridden their OS theme, as well as to toggle between the two, this is included in the header prior to the output of the html <body>...</body>:

//determines if the user has a set theme
function detectColorScheme(){
    var theme="light";    //default to light

    //local storage is used to override OS theme settings
    if(localStorage.getItem("theme")){
        if(localStorage.getItem("theme") == "dark"){
            var theme = "dark";
        }
    } else if(!window.matchMedia) {
        //matchMedia method not supported
        return false;
    } else if(window.matchMedia("(prefers-color-scheme: dark)").matches) {
        //OS theme setting detected as dark
        var theme = "dark";
    }

    //dark theme preferred, set document with a `data-theme` attribute
    if (theme=="dark") {
         document.documentElement.setAttribute("data-theme", "dark");
    }
}
detectColorScheme();

This javascript is used to toggle between the settings, it does not need to be included in the header of the page, but can be included wherever

//identify the toggle switch HTML element
const toggleSwitch = document.querySelector('#theme-switch input[type="checkbox"]');

//function that changes the theme, and sets a localStorage variable to track the theme between page loads
function switchTheme(e) {
    if (e.target.checked) {
        localStorage.setItem('theme', 'dark');
        document.documentElement.setAttribute('data-theme', 'dark');
        toggleSwitch.checked = true;
    } else {
        localStorage.setItem('theme', 'light');
        document.documentElement.setAttribute('data-theme', 'light');
        toggleSwitch.checked = false;
    }    
}

//listener for changing themes
toggleSwitch.addEventListener('change', switchTheme, false);

//pre-check the dark-theme checkbox if dark-theme is set
if (document.documentElement.getAttribute("data-theme") == "dark"){
    toggleSwitch.checked = true;
}

finally, the HTML checkbox to toggle between themes:

<label id="theme-switch" class="theme-switch" for="checkbox_theme">
    <input type="checkbox" id="checkbox_theme">
</label>

Through the use of CSS variables and JavaScript, we can automatically determine the users theme, apply it, and allow the user to over-ride it as well. [As of the current time of writing this (2019/06/10), only Firefox and Safari support the automatic theme detection]