Search code examples
javascripthtmlcssmedia-queries

How to override css prefers-color-scheme setting


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!


Solution

  • 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]