Logic and Language
Load the menuLoad the menu


Copyright   James R Meyer    2012 - 2020 https://www.jamesrmeyer.com

Note: Full functionality of this website requires JavaScript to be enabled in your browser.
 

How to setup a Dark mode switch on a web-site

If you would like to implement a dark mode option for your web-pages, this page explains a method of implementing a dark mode switch on a website, and which respects all possible visitor preferences. You will need to have more than a basic knowledge of HTML, CSS and JavaScript.

 

The first thing you should consider when investigating the possibility of implementing a dark mode on a website is that there are two possible dark modes available to the user:

  1. where the user has selected the browser/system Dark mode, and
  2. where the user has selected a Dark mode for a particular website by clicking a button on the page

Note that the browser’s Dark mode can be triggered by the operating system’s Dark mode, or, depending on the browser, it may be set independently. In the comments below, the browser/system mode may be referred to simply as the “system” (since we are trying to respect the user’s intentions, note that the user may override their operating system setting in their browser, but this will be seen by the web-page as the “system”).

 

If a web-page has a CSS media query section defined by:

then if the user has set the system mode as “dark”, then the page will display according to the code within this @media section. So, if you want a light/dark toggle switch on your website, one might imagine that the easiest way would be to “trick” the browser into thinking that the user has set his browser to be in dark mode for that website. But there doesn’t seem to be any way to do this, so we have to consider an alternative method of implementing dark mode, and which involves a bit more work. But that’s life!

 

I looked on the web to see how other people had implemented a dark mode. Many people have simply created their own dark mode which overrides the browser/system setting and gives the user no option to revert to the browser/system theme. I think that it is better to give the user the option to revert to the system setting and this method shows how to set up a choice of dark modes by assigning an appropriate id to the HTML tag. It is this id which defines the light or dark mode.

 

The method should work in all modern browsers. No attempt has been made to make it work in obsolete browsers such as Internet Explorer.

 

 

Details of the Code

The essential code required to provide the functionality of the method is quite small and is described below. Note that double-clicking inside a code box will select the code for copying. You can see a demo of the essential files at Basic Dark mode Demo page. For sites with Disqus, more code is required, you can see the essential code at Basic Dark mode Demo page for pages with Disqus comments - further details are given below. You can also download a zipped copy of all the demo files at dark-mode-demo.zip.

 

 

The HTML code

For this method all the HTML files initially have an idPdark” on their HTML tag (this is the default mode which is to follow the system setting), for example:

Putting this id in the HTML code ensures that the page will always respect the browser/system light/dark mode setting on the user’s first visit to the site and will continue to do so unless the user uses the light/dark switch on the page. If JavaScript is disabled the page will still respect the browser/system light/dark mode setting. The details of how this “Pdarkid operates will be described below.

 

 

The JavaScript Code In the Header

A small script is required in the head which sets the correct light/dark mode for the subsequent loading of the page. The reason for putting this in the head is that If you load the page first and then the JavaScript code, then if the page was loaded in light mode, but the user wants dark mode, then the page will flicker as it implements the change. This flicker will be worse on slow connections, so just because it’s okay for a fast connection, that doesn't mean it will be okay for all your visitors. There is no need to worry about a hit on performance as the amount of code required for this is very small. It operates by detecting if the user has overridden the system setting by a stored preference for the website (in which case the preference is stored in Local Storage), and then it sets the id on the HTML tag to the appropriate value (if the user has not overridden the system setting, then no action is required as the appropriate id is already set for this as the default case).

 

The code in the head is:

	

and the minified version is:

	

After this is run, the HTML tag will either have an id of “Dark” or “Pdark” or else have no id (and if JavaScript is disabled, the id will be “Pdark”).

 

 

The CSS code

Many people have suggested using CSS variables to implement a dark mode, and this seems to be a popular method. I have not used them so far for this purpose on my site as I have a lot of elements that need to be changed in order to get a reasonably acceptable dark mode appearance, and this would have meant a long list of CSS variables, with a concomitant problem of how to assign meaningful names to them. But for a site with a small total color set variables are probably a good option.

 

In any case, with this method you can choose to use CSS variables or not, or a combination, and both methods are demonstrated here. If you are using CSS variables, you can declare the default value as scoped within the body tag, for example: (Footnote: Note that you can still style the body element itself independently of these variables.)

 

The appropriate CSS for changing the display is written as the last part of the entire CSS code to take account of the CSS cascade. The elements which are to be changed for dark mode are set by prefixing with the #Dark term, for example:

 

The trick to enable both a user dark mode and the system dark mode is to replicate the #Dark CSS rules exactly within a “@media (prefers‑color‑scheme: dark)” media query, where every instance of #Dark is replaced by #Pdark, so that for every rule defined by #Dark, there is the same rule defined by #Pdark within the “@media (prefers‑color‑scheme: dark)” section, for example: (Footnote: Though it is not essential, I think it is easier to maintain things if one has multiple CSS files. If this is done then for the light/dark mode there are the standard CSS files followed by a dark.css file and a preferDark.css file, where the only difference between the two is that everything in the preferDark.css file is enclosed by the “@media (prefers‑color‑scheme: dark)” brackets, and every #Dark is changed to #Pdark. The files can easily be concatenated into a single file using a batch file and removing the extra instances of @charset… and can also be compressed for efficiency.)

This ensures that the appearance of the dark mode is the same for both the user selected dark mode and the system dark mode.

 

If JavaScript is not enabled, then if the user has set the system preference for dark mode, this will be implemented since each element to be changed will have their CSS for dark mode specified within the “@media (prefers‑color‑scheme: dark)” media query, while if the user has set their system preference as light mode, this section will have no effect.

 

 

The remaining JavaScript Code

Now we need to consider the JavaScript for implementing the initial setup. This can be called at the end of the HTML code. On document load we have the function that does the initial setup:

var GdarkSW; // Global variable to refer to the light/dark switch element

document.addEventListener('DOMContentLoaded', function () {

 // Set the initial state of the check-box
 setCheckBox();

 // Set the initial state of the reset button
 resetButton();

 // add an event listener to the toggle check-box which will detect user click
 // so that if the check-box is clicked, the code will be run to switch the theme
 try {
  GdarkSW = document.getElementById('Theme-switcher');
  GdarkSW.addEventListener('click', function (e) { switchTheme(); }, false );
 } catch (e) {
  // alert(e + '  line: ' + e.lineNumber + '  File: ' + e.fileName);
 }

 // Add listener to detect change in system dark mode setting, and if so calls the SystemThemeChanged function
 // If the system does not support matchMedia, there will be no trigger event.  
 try {  
  window.matchMedia('(prefers-color-scheme: dark)').addListener(SystemThemeChanged); 
 } catch (e) {
  // alert(e + '  line: ' + e.lineNumber + '  File: ' + e.fileName);
 }

 // We have a button to reset the mode to the system setting rather than user override
 // This adds a listener to the button to detect a click and call the function resetTheme
 try {
  document.getElementById('ResetTheme').addEventListener('click', function () { resetTheme(); }, false );
 } catch (e) {
  // alert(e + '  line: ' + e.lineNumber + '  File: ' + e.fileName);
 }
});

Next we have a function “setCheckBox” that sets the check-box to be correctly checked or unchecked according to whether the display is in dark or light mode, and a function “resetButton” that sets the Reset Button correctly to enabled or disabled.

 

If you want the style of your toggle switch to change according to whether the page display is dark or light, then the “setCheckBox” function is required. On the other hand, if your toggle switch is to appear the same in both cases, then this function is not required. Note that in most cases, you will not want the check-box to be seen, and this is achieved by using CSS position: absolute and positioning it outside of the visible view-port (or setting the opacity: zero and pointer‑events: none).

function setCheckBox(){
 try {
  // only cases where we are with dark display are if in system mode and prefers dark or when the use them is dark
  // for these cases we set the check-box to unchecked, otherwise checked
  GdarkSW.checked = true;
  if (Gtheme === 'dark'){ GdarkSW.checked = false; }
  // if we get an error in retrieving matchMedia, defaults to system light mode
  if(Gtheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches){
   GdarkSW.checked = false;
  }
 } catch (e) {
  // alert(e + '  line: ' + e.lineNumber + '  File: ' + e.fileName);
 }
}

function resetButton() {
  if (Gtheme === 'system') {
  document.getElementById('ResetTheme').classList.add('grayed');
  document.getElementById('ResetTheme').disabled = true;
 }
 else{
  document.getElementById('ResetTheme').classList.remove('grayed');
  document.getElementById('ResetTheme').disabled = false;
 }
}

Next we have a function “resetTheme” that resets the mode to the system default and sets the check box and Reset Button accordingly, and a function “SystemThemeChanged” which is triggered if the system light/dark mode is changed by the user - if the page is currently in system mode then the check-box may need to be set correctly.

function resetTheme() {
  localStorage.removeItem('LStheme');
  Gtheme = 'system';
  document.documentElement.removeAttribute('id');
  document.documentElement.setAttribute('id', 'Pdark');
  // need to make check-box switch display correctly
  setCheckBox();
  // reset the reset button
  resetButton();             
}

function SystemThemeChanged() {
  if (Gtheme === 'system') {
   setCheckBox();
 }
}

The next function is the function that actually changes the user mode between light and dark. First it has to detect the current mode, sets the global variable “Gtheme” accordingly, and sets the Reset Button to enabled. Then it sets the id of the HTML tag and stores the current theme in the Local Storage.

function switchTheme(e) {
 try {
  switch (Gtheme) {
   case 'system':
    // get system dark mode setting
    if (!window.matchMedia) {
     // if matchMedia method is not supported, the default is light mode, so we switch to dark.
     Gtheme = 'dark';
    } else {
     if (!window.matchMedia('(prefers-color-scheme: dark)').matches) {
      // if the system not set to dark, we switch to dark
      Gtheme = 'dark';
     } else {
      Gtheme = 'light';
     }
    }
    break;
   case 'light': // change to dark if it is in light mode
    Gtheme = 'dark';
    break;
   case 'dark': // change to light if it is in dark mode
    Gtheme = 'light';
    break;
   default:
    // change to light is the default
    Gtheme = 'light';
  }

  resetButton();

  // now setup according to what we have set Gtheme as
  // set the local storage so subsequent site pages will load as the correct mode
  if (Gtheme === 'light') {
   document.documentElement.removeAttribute('id');
   localStorage.setItem('LStheme', 'light');
  } else {
   document.documentElement.setAttribute('id', 'Dark');
   localStorage.setItem('LStheme', 'dark');
  }
 } catch (e) {
  // alert(e + '  line: ' + e.lineNumber + '  File: ' + e.fileName);
 }
}

 

Disqus Comments

If you have Disqus comments on your page, then things are somewhat more complicated. Disqus do not give an option to change the CSS of their comments in any way, so unless one wants to delve deep into the inner workings of Disqus, the only way to implement dark mode is to set the “Auto” option for light/dark mode. (Footnote: This can be set by the Disqus administrator in the Admin > Setting > General (at https://<your site>.disqus.com/admin/settings/general/ ) and set the Color scheme Appearance to auto.) The way this operates is somewhat obscure, but it seems to be the case that it follows according to the background color set for the element with the iddisqus_thread”.

 

Unfortunately, the Disqus comments do not automatically update to the correct mode when the user toggles the light/dark mode switch, and the only way I have found to get round this is to reload the Disqus comments. Because of this, I decided that the best option would be to hide the Disqus comment section and not load the comments unless the user clicks on a “Show comments” button. In this way the comments are only reloaded if they are already displayed. This has the further advantage that it speeds up the page load time and reduces the data loading for users that do not want to see the comments.

 

An additional consideration is that if a user changes the browser/system setting while the web-page is open, the Disqus comments will again not automatically update, and because of this, a Listener must be added to detect a change in the browser’s mode setting, and the Disqus comments will be reloaded if necessary. All these workarounds for the Disqus comments are necessitated by the fact that the Disqus system does not allow one to set the CSS of the comments which limits the options available. One can appreciate that Disqus wants to maintain a consistent profile across all pages on which it appears, but surely it is within their capabilities to provide a better system for light/dark modes?

 

The Basic Dark mode Demo page for pages with Disqus comments shows the basics of the code required if Disqus comments are on the web-page.

 

 

Finally…

Finally, thanks to Alex Gorbatchev for his SyntaxHighlighter system for displaying HTML, CSS, and JS code. As noted above, you can see a demo of the essential files at Basic Dark mode Demo page and you can download a zipped copy of all the demo files at dark-mode-demo.zip.

 

If you have any comments, suggestions, or questions about this Dark mode method, or require help in implementing the method, please feel free to contact me.

section divider

Footnotes:

section divider

 

 

Diverse opinions and criticisms are welcome, but messages that are frivolous, irrelevant or devoid of logical basis will be blocked. Difficulties in understanding the site content are usually best addressed by contacting me by e-mail. Note: you will be asked to provide an e-mail address - any address will do, it does not require verification. Your e-mail will only be used to notify you of replies to your comments - it will never be used for any other purpose and will not be displayed. If you cannot see any comments below, see Why isn’t the comment box loading?.

section divider
 

Copyright   James R Meyer    2012 - 2020
https://www.jamesrmeyer.com