Dark Mode with two CSS theme files

Basic Setup With no user toggle support

  • Two fully themed Bootstrap CSS are required
  • Filtered by stylesheet media filters
  • Based on advice by Thomas Steiner in Hello darkness, my old friend.
  • JavaScript fallback if the browser does not support dark-mode
  • Does not require jQuery

Replace the bootstrap stylesheet with the following code:

<!-- Bootstrap CSS -->
<!-- Inform modern browsers that this page supports both dark and light color schemes,
  and the page author prefers light. -->
<meta name="color-scheme" content="light dark">
<script>
  // If `prefers-color-scheme` is not supported, fall back to light mode.
  // i.e. In this case, inject the `light` CSS before the others, with
  // no media filter so that it will be downloaded with highest priority.
  if (window.matchMedia("(prefers-color-scheme: dark)").media === "not all") {
    document.documentElement.style.display = "none";
    document.head.insertAdjacentHTML(
      "beforeend",
      "<link id=\"css\" rel=\"stylesheet\" href=\"bootstrap.css\" onload=\"document.documentElement.style.display = ''\">"
    );
  }
</script>
<!-- Load the alternate CSS first ... -->
<link id="css-dark" rel="stylesheet" href="bootstrap-night.css" media="(prefers-color-scheme: dark)">
<!-- ... and then the primary CSS last for a fallback on very old browsers that don't support media filtering -->
<link id="css-light" rel="stylesheet" href="bootstrap.css" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: light)">

This is all you need to enable dark mode with Bootstrap.

You also have the flexibility to give the user control with a toggle switch, but that requires .

Additional Setup Giving user control

The basic principle is honouring the browser preference (which we assume the user set with intent), but also provide a switch in the UI. Once this switch has been clicked on, swap the color mode and remove browser based color scheme selection.

The following code will require jQuery:

<script>
  $(document).ready(function(){

    // Update the toggle button based on current color scheme
    function updateDarkToggleButton() {
      $dark = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);
      $("#css-toggle-btn").prop( "checked", $dark );
    }
    // Update on first load.
    updateDarkToggleButton();
    // and every time it changes
    if (window.matchMedia) window.matchMedia("(prefers-color-scheme: dark)").addListener( updateDarkToggleButton );

    // Color Scheme toggle botton

    // function to initialise the css
    function init_color_scheme_css($id, $mode) {
      if ($("#"+$id)) $("#"+$id).remove();  // remove exitsing id
      $("#"+$id+"-"+$mode).attr( {
        "data-href-light": $("#"+$id+"-light").attr("href"),  // store the light CSS url
        "data-href-dark": $("#"+$id+"-dark").attr("href"), // store the dark CSS url
        "data-color-scheme": $mode,  // store the mode, so that we don't re-initalise
        "media": "all",  // drop the media filter
        "id": $id  // rename the id (drop the `-{mode}` bit)
      } );
      $other = ($mode == 'dark') ? 'light' : 'dark';
      $("#"+$id+"-"+$other).remove();
    }

    // function to toggle the CSS
    function toggle_color_scheme_css($id, $mode) {
      // grab the new mode css href
      $href = $("#"+$id).data("href-"+$mode);  // use `.data()` here, leverage the cache
      // set the CSS to the mode preference.
      $("#"+$id).attr( {
        "href": $href,
        "data-color-scheme": $mode,
      });
    }

    // toggle button click code
    $("#css-toggle-btn").bind("click", function() {
      // get current mode
      // don't use `.data("color-scheme")`, it doesn't refresh
      $mode = $("#css").attr("data-color-scheme");
      // test if this is a first time click event, if so initialise the code
      if (typeof $mode === 'undefined') {
        // not defined yet - set pref. & ask the browser if alt. is active
        $mode = 'light';
        if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) $mode = 'dark';
        init_color_scheme_css("css", $mode);
        // `init_color_scheme_css()` any other CSS
      }
      // by here we have the current mode, so swap it
      $new_mode = ($mode == 'dark') ? 'light' : 'dark';
      toggle_color_scheme_css("css", $new_mode);
      // `toggle_color_scheme_css()` any other CSS
    });

  });
</script>

Remember that the above code does not include a persistence layer to remember a user toggled preference.

Read the README.md for more on this proof of concept.