js theme switcher

credits

My theme switcher code is a Frankenstein's monster of two different tutorials I found online: a theme switcher tutorial from Studytonight and a facepile snippet written by Stephanie Eckles.

instructions

This is the exact code I'm using[1] as of posting. If you want to do exactly what I am doing, this should basically work right out of the box. By exactly what I am doing, I mean:

  1. a light theme, a dark theme, and a high contrast theme
  2. stored in three files "/styles/light.css", "/styles/dark.css", and "/styles/contrast.css"
  3. using a switcher that is sticky at the bottom of a desktop screen and absolute at the top of a mobile screen

And to take it out of the box, so to speak, you're going to need to:

  1. Add the CSS to your stylesheet (plus whatever changes you want each theme to make to the three theme stylesheets)
  2. Add the tag <link rel="stylesheet" id="switcher-id" href=""> to the head of every single HTML file on your site.[2]
  3. If you have a JavaScript file that you're already using on every single HTML file, you can just add the JavaScript to that file. Otherwise you've got to:
    1. Make a JavaScript file in your site directory and add the JavaScript to that file.
    2. Add a script tag to every single HTML file on your site. If your script is called "script.js" and is in your site's parent directory, your script tag will look like <script src="/script.js"></script>

If you want to add a theme (say, "purple"), you'll need to make a file "/styles/purple.css". Then you go into the JavaScript and add one more "else if" statement that looks like the previous statements, except with the link to your new CSS file. You'll also want to add another switchesCode.HTML += '<div data-theme="contrast" class="switch" id="switch-4" class="tooltip" data-text="purple"></div>' statement.

In the CSS, you'll need to create a new root variable --purple, which you can set to any color (though presumably some shade of purple). You also have to change the --theme-count variable to 4 so as not to break the stack movement. You'll then add #switch-4 {background-image: var(--purple);}.

And boom! You've got a theme switcher!

code

JS

let switchesCode = { id: "switches", HTML: ""}
let switches = document.getElementsByClassName('switch');
let chosenTheme = localStorage.getItem('style');
let switchElement = document.getElementById(switchesCode.id);

function setTheme(theme) {
	if (theme == 'light') {
		document.getElementById('switcher-id').href = '/styles/light.css';
	} else if (theme == 'dark') {
		document.getElementById('switcher-id').href = '/styles/dark.css';
	} else if (theme == 'contrast') {
		document.getElementById('switcher-id').href = '/styles/contrast.css';
	}
	localStorage.setItem('style', theme);
}

switchesCode.HTML += '<div class="switch" id="switch-1" data-theme="light"></div>'
switchesCode.HTML += '<div class="switch" id="switch-2" data-theme="dark"></div>'
switchesCode.HTML += '<div class="switch" id="switch-3" data-theme="contrast"></div>'

switchElement.innerHTML = switchesCode.HTML;

if (chosenTheme == null) {
	setTheme('light');
} else {
	setTheme(chosenTheme);
}

for (let i of switches) {
	i.addEventListener('click', function () {
		let theme = this.dataset.theme;
		setTheme(theme);
		});
}

CSS

:root {
	--light: #FFF;
	--dark: #000;
	--contrast: linear-gradient(to right, #FFF 50%, #000 50%);

	--theme-size: 2rem;
	--theme-count: 3;
}

#switches {
	position: sticky;
	top: 92%;
	margin-bottom: -50px;
	margin-left: 1.5rem;

	display: grid;
	grid-template-columns: repeat(
		var(--theme-count),
		max(44px, calc(var(--theme-size) / 1.15))
	);

	padding: 0.08em;
	font-size: var(--theme-size);

	@media (any-hover: hover) and (any-pointer: fine) {
		grid-template-columns: repeat(
			calc(var(--theme-count) + 1),
			calc(var(--theme-size) / 1.75)
		);
	}

	@media screen and (max-width:1150px) {
		position: absolute;
		top: 0;
		margin-bottom: 0;
	}

	.switch {
		display: block;
		border-radius: 50%;
		width: var(--theme-size);
		height: var(--theme-size);
		cursor: pointer;
		transition: transform 180ms ease-in-out;
	
		&:hover ~ *,
		&:focus-within ~ * {
			transform: translateX(33%);
		}
	}
	
	#switch-1 {background-color: var(--light);}
	
	#switch-2 {background-color: var(--dark);}
	
	#switch-3 {background-image: var(--contrast);}

}

my themes

Feel free to ignore this part if you're familiar with CSS and already know the power of variables, but I thought I'd also share how I set up my themes.

With the exception of my little Wikipedia card and the similar style I set up to mimic my favorite Latin/Greek dictionary, all of the colors in my stylesheet are done with variables. I am a big variable person. I also use variables based on variables (e.g. --color-code: var(--yellow-transparent);). This makes changing theme colors super easy.

My light theme, which is the same color variables from my general stylesheet, is the following:

:root {
	--content-bg: var(--white);
	--main-text: var(--black);

	--yellow: #E4A21D;
	--orange: #DD7D2C;
	--red-orange: #D5573B;
	--red: #AF5447;
	--purple: #765087;
	--blue: #3F7CAC;
	--sea-green: #65816D;
	--green: #8B862D;

	--yellow: 228, 164, 29;
	--yellow-transparent: rgba(var(--yellow), 0.15);
}

Then my dark theme is basically just those variables switched around:

:root {
	--content-bg: var(--black);
	--main-text: var(--white);

	--yellow: #EFB43E;
	--orange: #EB934A;
	--red-orange: #E66E53;
	--red: #D16253;
	--purple: #9854B7;
	--blue: #4997D3;
	--sea-green: #61B77A;
	--green: #C8C02A;

	--yellow-rgb: 239, 180, 62;
	--accent-transparent: rgba(var(--yellow-rgb), 0.4);
}

body {
	background-color: #A6730C;
}

That's it. That's the whole file. Apart from switching up the color variables, the one and only addition I made is to set the background to be a dark gold instead of green.[3]

My high-contrast theme is similarly succinct:

:root {
	--content-bg: var(--white);
	--main-text: var(--black);

	--yellow: #AD7912;
	--orange: #AE5C19;
	--red-orange: #AA3A20;
	--red: #883A30;
	--purple: #5A356C;
	--blue: #2B5E85;
	--sea-green: #44684E;
	--green: #6B681E;

	--yellow: 173, 121, 18;
	--yellow-transparent: rgba(var(--yellow), 0.15);

	--main-size: 18px;
	--small-size: 16px;
}

a {
	text-decoration: underline solid 0.08em;
}

Here the two differences are that I've increased the font sizes (from 16px to 18px for the main and 14px to 16px for the small) and I've added underlines to all links.

But because I've used variables for my colors across the site, those little code snippets can accomplish the fairly dramatic changes you see when you change themes.


  1. Well, except that I replace my slightly off-black and off-white with true black and white.^
  2. If you code in VSCode (I recommend it), you can search/replace across all files in a directory. I added this line by searching for the existing line where I called my stylesheet and then replacing it with both that line and this one.^
  3. You will notice that this is not done with a variable. This is because I am using this color absolutely nowhere else in my entire site, and to be honest I still considered making it a variable just for consistency.^