In this tutorial we are going to learn how to create switch between dark and light theme using vanilla JavaScript and CSS. We will also be using browser localStorage to persist the theme.
So let’s get started with below HTML and CSS.
<html> <head> <title>Light / Dark Theme Switcher in Vanilla JavaScript and CSS</title> </head> <body> <header> <div class="logo">Brand</div> <nav> <ul> <li><a href="#home">Home</a></li> <li><a href="#news">News</a></li> <li><a href="#contact">Contact</a></li> <li><a href="#about">About</a></li> </ul> </nav> <button id="theme">Dark</button> </header> <main> <h1>Chocolate Chip Muffins</h1> <p> A muffin is an individual-sized, baked product. It can refer to two distinct items, a part-raised flatbread that is baked and then cooked on a griddle (typically unsweetened) and a cupcake-like quickbread (often sweetened) that is chemically leavened and then baked in a mold. While quickbread muffins are often sweetened, there are savory varieties made with ingredients such as corn and cheese. The flatbread is of British or European derivation, and dates from at least the early 18th century, while the quickbread originated in North America during the 19th century. Both are common worldwide today. </p> <h2>Muffin top</h2> <p> The muffin top is the crisp upper part of the muffin, which has developed a "browned crust that's slightly singed around the edges". In 2018, McDonald's restaurant announced they were planning to sell muffin tops as part of its McCafe breakfast menu. </p> </main> </body> </html>
and following CSS
* { margin: 0; padding: 0; } :root { --font: Sans-Serif; --font-size-1: 16px; --font-size-2: 32px; --font-size-3: 48px; --font-size-4: 56px; --font-weight-1: 400; --font-weight-2: 700; --font-weight-3: 900; --white: white; --black: black; } body { font-family: var(--font); font-size: var(--font-size-1); max-width: 960px; margin: 0 auto; } header { display: flex; justify-content: space-between; align-items: center; } header .logo { font-size: var(--font-size-2); font-weight: var(--font-weight-2); } nav ul { list-style-type: none; display: flex; } nav ul li { margin-right: 10px; } header #theme { border: 0; padding: 5px 10px; } h1, h2 { margin-top: 30px; margin-bottom: 10px; } h1 { font-size: var(--font-size-4); } h2 { font-size: var(--font-size-3); }
If you save, above markup in a file and load in browser you should see below output. Please make sure you load the page using a local server.
Set a data-theme
attribute on body
element as follows
<body data-theme=""></body>
We have not set any value to data-theme
attribute and will treat default/null is equal to light theme.
So if you save it and reload the browser tab you won’t see any difference.
Next, we will set onclick
handler of theme swicther button
<button id="theme" onclick="switchTheme()">Dark</button>
Now we have defined the onclick handler however we have not created the function yet. So lets create switchTheme()
function. You can open the script
tags within document or create the function in separate file.
Before we author that function lets gather our requirements
theme
property - on inital load this won’t exist so, it will return null. We are using localStorage as we need to persist the theme betwen various page loads. If current theme is light
update the theme value to dark
or vice-versa.theme
variable.data-theme
attribute defined in step#1 abovefunction switchTheme() { // toggle theme const toggleValue = localStorage.getItem('theme') === 'dark' ? 'light' : 'dark' // update localstorage value; localStorage.setItem('theme', toggleValue) // set current theme - default to `light` on first load and set `theme` property in localStorage. const currentTheme = localStorage.getItem('theme') || 'light' // update current theme value on `data-theme` attribute document.body.dataset.theme = currentTheme }
So, our switchTheme()
function is ready. Now, we need to handle the scenario when the website is loaded the first time - again there are two cases here listed below
So let’s handle the above two scenarios in function setTheme()
and here are our detailed requirements
theme
is not found then default it to light
on the first loaddata-theme
attribute that we created in step#1 above.setTheme()
function on initial page loadconst themeSwitcher = document.querySelector('#theme') function setTheme() { // set current theme - default to `light` on first load and set `theme` property in localStorage. const currentTheme = localStorage.getItem('theme') || 'light' // update current theme value on `data-theme` attribute document.body.dataset.theme = currentTheme // update inner text of button dynamically based on current theme themeSwitcher.innerText = currentTheme === 'light' ? 'Dark' : 'Light' } // set theme on inital load setTheme()
As you can see few of steps are common between the switchTheme()
function and setTheme()
function so, its good time to refactor switchTheme()
function and below is updated code.
We have removed last 2 lines and called setTheme()
function instead
function switchTheme() { // toggle theme const toggleValue = localStorage.getItem('theme') === 'dark' ? 'light' : 'dark' // update localstorage value; localStorage.setItem('theme', toggleValue) // update theme setTheme() }
We have covered the JavaScript part here. Now if you open the developer console and go to the Application
tab, Local Storage
section and then click on the theme switcher button you should see the theme being updated at each click.
Now we just need to add magic of CSS data attributes here to respond to theme switcher button.
[data-theme='dark'], [data-theme='dark'] a { color: var(--white); background: var(--black); } [data-theme='light'] #theme { color: var(--white); background: var(--black); }
You can use any color however, I just used two colors i.e. white
and black
.
data-theme
is set to dark
, we are setting the font color to white
and background
color to black
. No action is required when the theme color is light
as the browser will default the font color to black
and background color to white
.A working demo of code discussed above