RAINBOW RANGE TUTORIALS
Introduction
In this tutorial we're going to go over how to code a dark theme & light theme toggle for a personal website.
You don't have to use them, but I use fontawesome icons in this tutorial. You could use any readable characters or symbols you want, this is just for demonstration.
Create A Dark Theme in Your Stylesheet
The first thing we should do is create a dark theme in our CSS stylesheet. This is, in my opinion, the funnest part of the process. I use CSS variables to define the colors for both the dark and light themes, this is just a more modern and maintainable approach, but you can use any method you prefer. I'll show you how to do it both ways:
First, we will target the :root element with our light (default) theme using variables:
:root {
--bg: #94D6FF;
--container:rgba(0, 153, 255, 0.69);
--container-inner:#00409E;
--font:#005dc7;
--link:#005dc7;
--link-hover:#00edff;
--header:#0068ff;
--stroke:#7df4ff;
--mix-blend-mode:hue;
--background-image:url('/img/site-graphics/rainbow.jpg');
}
You can add as many or as little variables as you need for your personal website. The styles on Rainbow Range are fairly noncomprehensive compared to my personal site, frammyjammy.com.
Next, we will define the dark theme by targeting the .dark-mode class using the same variables, with different values:
.dark-mode {
--bg: #00047a;
--container:rgba(1, 52, 160, 0.69);
--container-inner:#003269;
--font:#C3DFEB;
--link:#62abff;
--link-hover:#00edff;
--header:#5498FF;
--stroke:#0157b2;
--mix-blend-mode:multiply;
--background-image:url('/img/site-graphics/night.jpg');
}
To apply variables to elements, you can use the CSS var() function:
body {
background-color: var(--bg);
color: var(--font);
}
Alternatively, for even simpler websites, you can define the colors directly in the :root and .dark-mode class without using CSS variables:
:root {
background-color: #94D6FF;
color: #005dc7;
}
.dark-mode {
background-color: #000120;
color: #C3DFEB;
}
But you'll need to define the colors for every element that changes between themes. Like, say, only on dark mode you want to color your links red, and make them green on hover. You would write:
.dark-mode a {
color: red;
}
.dark-mode a:hover {
color: green;
}
Defining these CSS rules manually for each element is slightly tedious and can quickly become unmanageable. This is why I strongly recommend using CSS variables for theme management.
Prevent a Light Theme Flash
First JavaScript step, let's create a small JS file telling the site to initialize the dark theme. This is something I figured to do after I noticed a flash of the light theme on each new page load.
Create theme-init.js (ideally placed inside a /js/ directory) with the following code:
const savedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (savedTheme === "dark" || (!savedTheme && prefersDark)) {
document.documentElement.classList.add("dark-mode");
}
With this code block, we are asking the website if the user has a preference for dark mode or has saved the dark theme in their localStorage by toggling it manually on site. If so, it will add the "dark-mode" class from our stylesheet to the <html>.
Now add this script to the <head> of your HTML document as a normal script, without defer. A normal script runs as soon as the browser reaches it, before the page is painted, so the .dark-mode class is already on the <html> element when the first frame is drawn. A deferred script waits until the HTML has been parsed, so the page can briefly show the light theme first, which causes the flash. I put mine just above my stylesheet, but the important part is that it isn't deferred.
So, your code should look something like this:
<head>
<script src="/js/theme-init.js"></script>
<link href="/css/style.css" rel="stylesheet" type="text/css" media="all">
<script src="/js/static.js" defer></script>
</head>
Font Awesome Icons
Font Awesome is a wonderful resource I discovered through W3Schools. They have a robust array of free to use icons that cover most needs on a personal website. They also have a paid version with even more icons. I tend to stick with free stuff though, so we'll be using the free version here.
To use Font Awesome icons, visit the fontawesome.com/start page, enter your email into the form and click "Start For Free". You will be given a script tag to add to your HTML document. Add it to the <head> of your document, and you can now use any of the free icons on your website. Easy!
Here's what your code should look like:
<script src="/js/theme-init.js"></script>
<!-- STYLE -->
<link href="/css/style.css" rel="stylesheet" type="text/css" media="all">
<link href="/css/tutorials-and-resources.css" rel="stylesheet" type="text/css" media="all">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<script src="https://kit.fontawesome.com/YOUR_KIT_CODE.js" crossorigin="anonymous"></script>
Or do it the old school way, like I do here on Rainbow Range, by using a <link> tag:
<script src="/js/theme-init.js"></script>
<!-- STYLE -->
<link href="/css/style.css" rel="stylesheet" type="text/css" media="all">
<link href="/css/tutorials-and-resources.css" rel="stylesheet" type="text/css" media="all">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
Add the Toggle Button
Now we need to add the actual button that triggers the JavaScript functions. This is as simple as adding a button element to our HTML:
<button type="button" class="darkToggle dark-btn" aria-label="Toggle dark mode" aria-pressed="false">
<i class="fa fa-moon-o" aria-hidden="true"></i>
</button>
You can place this button anywhere on your website, but it's generally the best practice to place it near the top of the page, like here on Rainbow Range, it's in the upper left-hand corner. You could also place it in your top navbar or near the top of a sidebar. Just keep it near the top for best readability and UX.
You can name your class darkToggle or anything you like, as long as it's consistent throughout your code. We only need the darkToggle class to make the toggle work. It's what connects our button to the JavaScript. The dark-btn class is used for styling the button in our CSS.
The aria-label attribute is used for accessibility, it describes the button's function to screen readers. The aria-pressed attribute is used to indicate the button's state, whether it's pressed (dark mode) or not (light mode). This will be updated by our JavaScript.
The <i class="fa fa-moon-o" aria-hidden="true"></i> element is the default icon we see while in light mode (our default theme). It's standard to show a moon indicating you can switch to dark mode by clicking the button and vice versa.
Now we need to style the button so it looks nice, matches our design, and is easy to click. Here's the CSS I use for my button here on Rainbow Range:
/*-- DARK/LIGHT button toggle styles --*/
.dark-btn {
color: var(--link);
background-color: var(--container) !important;
position:fixed;
border: none;
cursor: pointer;
font-size: 22px;
margin:10px;
padding: 20px;
border-radius:100%;
z-index:999;
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2), inset 8px 7px 7px 1px rgba(255, 255, 255, 0.1);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.dark-btn:hover {
color: var(--font);
}
You can style your button to whatever matches your website design the best. Just be sure to make it easily identifiable and accessible.
Add the Toggle JavaScript
Now, let's create a separate JavaScript file for our actual toggle functionality. Or, what I do, is to place this function inside my universal script (such as a script.js file). But creating a theme-toggle.js file is also a good and maintainable approach.
This script will listen for clicks on the toggle button, switch the theme, and update the icon accordingly.
First, in our script, we need to define our variables with const.
// DARK LIGHT TOGGLE
const toggleButtons = document.querySelectorAll(".darkToggle");
const htmlEl = document.documentElement;
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
We first select all elements with the specified class .darkToggle, then we get a reference to the <html> element, and finally check the user's system theme preference.
See? JavaScript is not so scary! We are just talking to the computer (website) in a way it understands.
Next, we call updateIcons to set the initial state of the icon to match the active theme on page load:
updateIcons(htmlEl.classList.contains("dark-mode"));
This line checks if the <html> element has the .dark-mode class applied and tells the site to update the icons accordingly. This is the trigger for the updateIcons function.
Then, we tell the site to listen for changes if the user updates their system settings while browsing:
systemTheme.addEventListener("change", (event) => {
if (localStorage.getItem("theme")) return;
htmlEl.classList.toggle("dark-mode", event.matches);
updateIcons(event.matches);
});
Now we attach a click listener (we're telling the button to "listen" for clicks) so the user can toggle the classes manually on site, saving their preference to localStorage so it persists across sessions and pages:
toggleButtons.forEach(btn => {
btn.addEventListener("click", () => {
htmlEl.classList.toggle("dark-mode");
const isDark = htmlEl.classList.contains("dark-mode");
localStorage.setItem("theme", isDark ? "dark" : "light");
updateIcons(isDark);
});
});
Finally, the logic to update the FontAwesome classes based on the active theme state:
function updateIcons(isDark) {
toggleButtons.forEach(btn => {
const icon = btn.querySelector("i");
btn.setAttribute("aria-pressed", isDark);
if (icon) {
icon.classList.toggle("fa-sun-o", isDark);
icon.classList.toggle("fa-moon-o", !isDark);
}
});
}
We are asking the computer if the theme is dark (isDark), and if so to replace the moon icon with the sun icon. Otherwise, (!isDark--note the exclamation mark) replace the sun icon with the moon icon.
Place Your Theme Toggle Script in Your HTML
Now we need to place our theme toggle script in our HTML document. If you created a separate theme-toggle.js file, you can add it to your HTML like this:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EASY DARK / LIGHT THEME TOGGLE | TUTORIAL</title>
<script src="/js/theme-init.js"></script>
<!-- STYLE -->
<link href="/css/style.css" rel="stylesheet" type="text/css" media="all">
<link href="/css/tutorials-and-resources.css" rel="stylesheet" type="text/css" media="all">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- SCRIPTS -->
<script src="/js/theme-toggle.js" defer></script>
</head>
If you placed the toggle function inside your universal script, just replace "theme-toggle.js" with the path to your universal script. Be sure to add the defer attribute to this script tag so it doesn't run before the HTML is parsed.
And that's it! Here is the complete script all put together for easy copy and pasting into your own site—though I recommend actually following the steps if you want to understand how it works:
Complete Code For Copy & Paste
const toggleButtons = document.querySelectorAll(".darkToggle");
const htmlEl = document.documentElement;
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
updateIcons(htmlEl.classList.contains("dark-mode"));
systemTheme.addEventListener("change", (event) => {
if (localStorage.getItem("theme")) return;
htmlEl.classList.toggle("dark-mode", event.matches);
updateIcons(event.matches);
});
toggleButtons.forEach(btn => {
btn.addEventListener("click", () => {
htmlEl.classList.toggle("dark-mode");
const isDark = htmlEl.classList.contains("dark-mode");
localStorage.setItem("theme", isDark ? "dark" : "light");
updateIcons(isDark);
});
});
function updateIcons(isDark) {
toggleButtons.forEach(btn => {
const icon = btn.querySelector("i");
btn.setAttribute("aria-pressed", isDark);
if (icon) {
icon.classList.toggle("fa-sun-o", isDark);
icon.classList.toggle("fa-moon-o", !isDark);
}
});
}
I started out copying and pasting too, for my CSS, HTML, and JavaScript files. Now, I prefer to actually read the tutorials and forums I get my code from. It's best to understand the code you are using to the best of your ability. Once you understand the code, you can modify it to your needs and make it your own. This is how I learned to code, and I hope this tutorial helps you too!