Logic and
Language
Load the menuLoad the menu


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

How to setup Dark mode on a web-site

Page last updated 2 Apr 2024

 

If you would like to implement a dark mode option for your web-pages, this page explains a method of adding a dark mode to 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 following, 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 download copies of the basic demo files here:

 

And you can download copies of the disqus demo files here:

 

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 for 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
	resetThemeButton();

	// 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 “resetThemeButton” 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 resetThemeButton() {
	if (Gtheme === 'system') {
	document.getElementById('ResetTheme').classList.add('grayed');
	document.getElementById('ResetTheme').classList.add('disabled');
	}
	else{
	document.getElementById('ResetTheme').classList.remove('grayed');
	document.getElementById('ResetTheme').classList.remove('disabled');
	}
}

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
	resetThemeButton();
}

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';
	}

	resetThemeButton();

	// 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);
	}
}

 

2-way switch vs 3-way switch

This implementation of changing dark/light mode uses a check-box as a 2-way switch which renders every page on the site either dark or light, regardless of the system setting. I also provide a separate option to revert to the system setting. A reader has suggested (see the comments) that a 3-way switch is preferable, giving the three options of always dark, always light, or follow system setting.

 

However, the vast majority of users use a fixed system setting of either light or dark, which means that these users won't see any difference when the 2-way switch is set to dark and their system setting is also set to dark, or when the 2-way switch is set to light and their system setting is also set to light. The afore-mentioned reader argued that some users might have their system setting set to light mode at some times of the day, and to dark mode at other times. However, I think that the simplicity and compactness of the 2-way switch is preferable for the vast majority of users, and providing there is an option to revert to system setting then every user is catered for. On my own site I have now implemented a message that reminds users that there is an option to reset to system settings and which will only appear the first two times the dark/light mode is changed. It requires a HTML element that has the id of DarkMsg and which contains the message; the JavaScript switchTheme function is modified and also includes a new function fadeOutEffect. If you are interested in implementing this, the demo and files can be seen at Simple Dark mode Demo page with Reset message.

 

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.

 

Why I have deleted Disqus from my site

At first Disqus seemed like an easy solution for a comments system, but having used it for a while, I decided that it was time to ditch it. I have had numerous problems of comments disappearing after I made changes to my site. In Disqus you do not have access to the base data and you can only use an interface that only lets you see what it wants you to see. There are other big issues also, one is that unless you add a button to load Disqus only when the user clicks the button, it will load on every page load, and this uses an unbelievable amount of extra resources. Apparently it loads advertisements also, though I have not seen these adverts on my site or other sites, probably because I use a good ad block setup.

 

There are plenty of other comment systems out there, but most of them are paid, and those that are free get their revenue either by advertising or harvesting personal data or both.

 

A disadvantage of having a company managing your comments on a paid basis is that it can lock you in, so that if the prices increase, you may have difficulty in changing to another provider. But the main reason I changed is that I do not want to pay some company for the privilege of having people post material on my site, where the company controls the data, where I do not have full control over it. I don’t want to support personal data harvesting either.

 

So I looked around and found a free alternative over which I have full control and full access to the comment data. The system I use now uses code by Jacob Barkdull and you can see the details at HashOver Comment System. It isn’t plug and play, but if you are reasonably competent with some coding, it’s not a big problem to set it up. My biggest problem in moving over from Disqus was sorting out the mess of the comments and formatting it to import it into the new system.

 

Other people have posted articles on why they deleted Disqus too, see Why I Deleted Disqus and Why You Should Too, Switching Away from Disqus Review, Disqus Ads Are No Longer Free to Disable.

 

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 the links for downloads are above (Download links).

 

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.

Footnotes:

Interested in supporting this site?

You can help by sharing the site with others. You can also donate at Go Get Funding: Logic and Language where there are full details.

 

 

As site owner I reserve the right to keep my comments sections as I deem appropriate. I do not use that right to unfairly censor valid criticism. My reasons for deleting or editing comments do not include deleting a comment because it disagrees with what is on my website. Reasons for exclusion include:
Frivolous, irrelevant comments.
Comments devoid of logical basis.
Derogatory comments.
Long-winded comments.
Comments with excessive number of different points.
Questions about matters that do not relate to the page they post on. Such posts are not comments.
Comments with a substantial amount of mathematical terms not properly formatted will not be published unless a file (such as doc, tex, pdf) is simultaneously emailed to me, and where the mathematical terms are correctly formatted.


Reasons for deleting comments of certain users:
Bulk posting of comments in a short space of time, often on several different pages, and which are not simply part of an ongoing discussion. Multiple anonymous user names for one person.
Users, who, when shown their point is wrong, immediately claim that they just wrote it incorrectly and rewrite it again - still erroneously, or else attack something else on my site - erroneously. After the first few instances, further posts are deleted.
Users who make persistent erroneous attacks in a scatter-gun attempt to try to find some error in what I write on this site. After the first few instances, further posts are deleted.


Difficulties in understanding the site content are usually best addressed by contacting me by e-mail.

 

Based on HashOver Comment System by Jacob Barkdull

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