You might have noticed that nowadays, many websites have a theme switcher on top of their website. The theme switcher can also be used as a dark mode switcher. In this article, we’ll be building a theme switcher like that.
I am not going to focus a lot on the styles; instead, we’ll try to understand the logic about creating it. Also, the theme switcher will be persistent. The selected theme will be stored in the browser and will apply the same theme even after reloading. We’ll implement this using the browser’s local storage. So, let’s start building.
Things you need to know
- Basic HTML - Learn HTML
- Basic CSS - Learn CSS
- Little JavaScript - Learn JavaScript and you are good to go.
Here's our Complete App
Complete Theme Switcher One thing I must explain before we start building the Markup is custom data attributes in HTML.
What are HTML Data Attributes?
There may be situations when we have to store some information associated with DOM elements. Let's take an example, suppose we have a list of employees and we want to save their IDs, which we can use to manipulate them using the DOM. Before HTML5, we could have used class or IDs, but this is not an impressive solution. HTML5 introduced us to the concept of Custom Data Attributes. Any attribute that starts with data-
is a custom data attribute. We also have special HTML data
tags in HTML5.
For example, we can name data-identity
to create a custom data attribute for employee ID. Two things to keep in mind while creating a data attribute is that the value that is stored inside data attributes can only be of string type. And the second point is that we cannot use any valid HTML attribute as a name for data attributes. Like, data-id
is not appropriate.
We can access these attributes in JavaScript with dataset
property.
Suppose we want to access the identity attribute from before. We can access this using the below codes,
let employee = document.getElementByClassName("employee-table");
let empID = employee.dataset.identity;
// Or
let empID = employee.dataset['identity']
Now that you have a basic understanding of the data attributes, let's start building our Theme Switcher.
File Structure:
|--themes
|------dark.css
|------light.css
|------purple.css
|------sky.css
|--index.html
|--script.js
|--style.css
Now that we know the file structure, let's begin the coding.
Building the Markup
As you can understand from the gif above, we are not using any high-level design. We have a simple header section with the name of our app and the buttons to change the themes. Our body contains an image and some random texts. We are also using the Roboto font to style the document.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Theme Switcher</title>
<link rel="stylesheet" href="./style.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" id="switcher-id" href="">
</head>
<body>
<header>
<h1>???? THEME SWITCHER</h1>
<div class="theme-switches">
<div data-theme="light" class="switch" id="switch-1"></div>
<div data-theme="sky" class="switch" id="switch-2"></div>
<div data-theme="purple" class="switch" id="switch-3"></div>
<div data-theme="dark" class="switch" id="switch-4"></div>
</div>
</header>
<div class="container">
<div class="box">
<img src="https://images.unsplash.com/photo-1597926588114-2d9c1190b5c7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=923&q=80"
alt="Placeholder" class="image">
<div class="text">
<h3>A Sweet Heading</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
That is just a few lines of Markup. We have added the fonts in the header, default styles stylesheets, and a stylesheet link with a blank href
. This blank href
will be used to attach our theme designs. You can also see we are using a data-theme
custom data attribute to keep track of the theme selected. We will use the IDs of theme switches to design the switches. In our main body, we are using an image from Unsplash. And some random text. And finally, in the end, we are linking our script
file.
Styling Our Default CSS
As you might have observed, we have two stylesheets tag in our HTML document. The first stylesheet is responsible for all the default styles our website will have, like the font-sizes, viewport height-width, image sizes. And the second one will be responsible for the specific theme designs.
We’ll also use CSS variables in this example. The benefits of using variables are that we can declare a variable once and use it multiple times in a document. Declaring variables in CSS are pretty easy. We usually define them in the :root
selector. Any name that starts with --
is a CSS variable. Example,
root {
--text-color: #000;
--back-color: #eee;
}
In our default stylesheet, we will be declaring four variables that will be used for the theme switches. Then we'll reset our document.
We'll use flex-box to design the theme switches. The display:flex
property set on the parent element will make the child elements horizontal. The justify-content:center
will center the elements.
For the growing effect on the switches, we are using the CSS scale
transform property. We are scaling the element 1.2 times the original. And for the smooth effect, we are using transition: 0.3s all;
. Then, we are using our variables to design the switches. We are adding the variables as background-color
on the switches to design them.
You can check the code, and you'll get a clear idea. We are also using some basic media-queries for responsiveness. I think I don't have to explain the whole code because it is pretty basic. And I've discussed the portions I thought needs some explanation. Here's the full CSS code.
:root {
--light: #ffffff;
--sky: #7f9cf5;
--purple: #97266d;
--dark: #81899b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
height: 100vh;
overflow-x: hidden;
}
header {
text-align: center;
font-size: 30px;
padding: 50px 0;
}
.theme-switches {
display: flex;
justify-content: center;
}
.switch {
border: 2px solid black;
border-radius: 50px;
height: 30px;
width: 30px;
margin: 10px;
cursor: pointer;
}
.switch:hover {
transform: scale(1.2);
transition: 0.3s ease-in-out;
}
#switch-1 {
background-color: var(--light);
}
#switch-2 {
background-color: var(--sky);
}
#switch-3 {
background-color: var(--purple);
}
#switch-4 {
background-color: var(--dark);
}
.container {
max-width: 80%;
margin: 0 auto;
}
.box {
display: flex;
justify-content: space-around;
margin-top: 100px;
}
.box img {
max-height: 300px;
width: auto;
border-radius: 20px;
margin: 20px;
}
.text {
width: 50%;
}
.text h3 {
font-size: 35px;
padding: 30px 0;
font-weight: 600;
}
.text p {
font-size: 20px;
font-weight: 400;
}
/* Media Query */
@media (max-width: 768px) {
header h1 {
font-size: 25px;
}
.box {
display: flex;
flex-wrap: wrap;
}
.box img {
max-width: 80vw;
}
.text {
width: 80%;
}
}
Styling Our Themes
Now, let's set our theme files.
First, let’s understand the properties that change when we change a theme. Let's make a list of them.
- Heading Background
- Text Colors
- Image Border
- Body Background Color
Now that we've outlined the colors we’ll be changing, most of our work is done. First, create a file called
light.css
for the light styles.
light.css
:root {
--back-color: #eee;
--primary-color: #000000;
--header-back: #5d5d5d;
--header-text: #eee;
}
body {
background-color: var(--back-color);
color: var(--primary-color);
}
header {
background-color: var(--header-back);
color: var(--header-text);
}
.box img {
border: 2px solid var(--primary-color);
}
As you can see, we've defined the colors we'll be using in the light theme as variables. Then we are just adding the colors to the properties we want to change. Pretty simple, right? I am attaching the other theme files below,
sky.css
:root {
--back-color: #c3dafe;
--primary-color: #3c366b;
--header-back: #5f718d;
--header-text: #eee;
}
body {
background-color: var(--back-color);
color: var(--primary-color);
}
header {
background-color: var(--header-back);
color: var(--header-text);
}
.box img {
border: 2px solid var(--primary-color);
}
purple.css
:root {
--back-color: #fed7e2;
--primary-color: #702459;
--header-back: #874f5e;
--header-text: #eee;
}
body {
background-color: var(--back-color);
color: var(--primary-color);
}
header {
background-color: var(--header-back);
color: var(--header-text);
}
.box img {
border: 2px solid var(--primary-color);
}
dark.css
:root {
--back-color: #81899b;
--primary-color: #f9ffee;
--header-back: #1a1d24;
--header-text: #eee;
}
body {
background-color: var(--back-color);
color: var(--primary-color);
}
header {
background-color: var(--header-back);
color: var(--header-text);
}
.box img {
border: 2px solid var(--primary-color);
}
As you can see, all our themes have the same styles, we’re just changing the color variables, and everything is ready.
The JavaScript Part
Here comes the most crucial part. Our JavaScript code is responsible for all the theme-changing things. Let’s first understand the logic behind our script file.
First, we’ll loop through all the switches and store the dataset value into a variable. Then, we’ll pass the value into a function that will set the theme accordingly.
We’ll get all the switches used in HTML using their class name and store those into a variable called switches
.
let switches = document.getElementsByClassName('switch');
Then we’ll loop over the switches and will listen for the click event on the switches. We’ll store the dataset value clicked inside a variable called theme
in our case. I’ll be using a for...in
loop here. If you are not familiar with for...in
loop, check this article.
for (let i of switches) {
i.addEventListener('click', function () {
let theme = this.dataset.theme;
console.log(theme);
});
}
For your understanding, I’m first console logging the value that is clicked. Every time we click on a switch, it’ll console.log
its custom data attribute value. You can see the gif to get an idea.
Console Logging Values
Now we have to create a function called setTheme
, which will set our theme. Let's see how it looks,
function setTheme(theme) {
if (theme == 'light') {
document.getElementById('switcher-id').href = './themes/light.css';
} else if (theme == 'sky') {
document.getElementById('switcher-id').href = './themes/sky.css';
} else if (theme == 'purple') {
document.getElementById('switcher-id').href = './themes/purple.css';
} else if (theme == 'dark') {
document.getElementById('switcher-id').href = './themes/dark.css';
}
}
We are passing the parameter which we got from the dataset
into this function. We can also achieve the same with a switch case
statement. Our blank stylesheet in the HTML has the switcher-id
id in it. We are targetting that element and adding an href
to it. We are keeping all our theme styles in a separate folder called themes
. And we have to call this function from the loop. So, our final code for the loops will be,
for (let i of switches) {
i.addEventListener('click', function () {
let theme = this.dataset.theme;
console.log(theme);
setTheme(theme);
});
}
I think it's making sense now. We are almost done. The final thing is to implement the local storage here. The stored data in local storage is saved across browser sessions, and it is pretty similar to session storage. Though, the significant difference between these is, local storage does not have any expiry. Local storage creates a key-value pair to store data.
The localStorage.getItem
, with a name parameter, will create a key to save our values. In our example, we’ll be using a key called theme
. So, our first step is to initiate it.
let style = localStorage.getItem('style');
The default property of a local storage element is null
. So, our next step is to check if it’s null
and if it is, we will set a theme value for it.
if (style == null) {
setTheme('light');
} else {
setTheme(style);
}
Here, if the value is null
, we are setting the light theme as default. Otherwise, it'll set the value that is stored in the local storage.
And finally, We have to set the value as per the clicked switch. We’ll achieve this using the setItem
property of local storage.
localStorage.setItem('style', theme);
We’ll add this code before the end of the setTheme
function. So, our complete script.js
file will look like this,
let switches = document.getElementsByClassName('switch');
let style = localStorage.getItem('style');
if (style == null) {
setTheme('light');
} else {
setTheme(style);
}
for (let i of switches) {
i.addEventListener('click', function () {
let theme = this.dataset.theme;
setTheme(theme);
});
}
function setTheme(theme) {
if (theme == 'light') {
document.getElementById('switcher-id').href = './themes/light.css';
} else if (theme == 'sky') {
document.getElementById('switcher-id').href = './themes/sky.css';
} else if (theme == 'purple') {
document.getElementById('switcher-id').href = './themes/purple.css';
} else if (theme == 'dark') {
document.getElementById('switcher-id').href = './themes/dark.css';
}
localStorage.setItem('style', theme);
}
To learn more about the local storage web API, check this MDN article. You can see the value of local storage from the Inspect-->Application-->Local Storage
. We can check the gif below to see how it changes.
Theme Changing
I hoped you learned something new in this article. Feel free to comment on your thoughts. And you can find the complete source code here.