Making a theme switcher: Because even a theme can be fluid [Part 1]

Hero Image
Rishi Khan
February 14, 2025

·

7 min read
medium_u1659565234_An_image_of_two_equal_colours_one_being_light_and_d27976b1-fa5a-40d7-b906-75854c38c4cd_3.png

Have you ever needed to join the hype train of adopting dark mode on your website due to social (developer) pressures or a manager that just thinks "we can't fall behind the competition ๐Ÿ™„". But you love the clean and minimal look of the light theme that brightens up your day in this dark corporate world. Plus you are under the impression that computer screens are best viewed in waking hours to align with the natural circadian rhythm but we can't exclude our beloved night owls who churn coffee into code during the vampire hours.

Well i got you.

We are going to make a simple theme switcher component that can update all your theme colours and corresponding photos if needed. It can be extended to anything really on your website that needs to change with the theme such as font size (weird flex but ok) or the animations that are displayed (better flex).

Most theme switchers will use the data-theme class in css where you would have to hardcode the values for each theme inside your project, leading you to have to rebuild your project if you ever wanted to update your theme colours on whim. While this may be sufficient for a large majority, i am aiming at those with a penchant for being undecided especially when it comes to colours.

Keeping race out of it, i believe we should have the freedom to choose which colour we like at any moment. So what i will show you is how to update those colours on your API server, such as your CMS.

The key thing to note is that this switcher is a react hook and will only work on client components. As such this is a benefit since having server components update when you switch themes would required a window reload to update the data in the component while the client component and it's children would be updated at the change of state of the switch.

Let's get to switching your themes easily as switching your limiting beliefs and political opinions.

Step 1: The Setup (just like your last date)

In your terminal run these commands to start a basic nextjs project and then navigate to the newly created folder.

When running the npx command to set up the project, accept the defaults for now. You can experiment this later in your own time. Or read up more about them here.

1npx create-next-app@latest theme-switcher-tutorial 2cd theme-switcher-tutorial

Install the react-switch dependency. I like this package because it is maintained and offers easy to use features and integration. So much simpler than building your own toggle from scratch, ain't nobody got time for that.

In your terminal type:

1npm iย react-switch

Step 2: Create a reset.css and global.css file

Personally, i like using css modules for much better organisation of styles for my components as well as css layers for ensuring that the proper cascading of css styles are applied to the components when rendered.

Place these files in the src/app/styles folder.

1/*reset.css*/ 2@layer reset { 3 /* Reset margins and padding */ 4 * { 5 margin: 0; 6 padding: 0; 7 border: 0; 8 font-size: 100%; 9 font: inherit; 10 vertical-align: baseline; 11 box-sizing: border-box; 12 } 13 14 /* HTML5 display-role reset for older browsers */ 15 article, 16 aside, 17 details, 18 figcaption, 19 figure, 20 footer, 21 header, 22 hgroup, 23 menu, 24 nav, 25 section { 26 display: block; 27 } 28}
1/*global.css*/ 2 3@layer root { 4 :root { 5 --color-font: #303030; 6 --color-primary: #fff; 7 --color-secondary: #303030; 8 --color-accent: #2bb1a5; 9 --color-disabled: #9f9f9f; 10 [data-theme='dark'] { 11 --color-font: #fff; 12 --color-primary: #303030; 13 --color-secondary: #fff; 14 } 15 } 16 17 * { 18 transition: background-color 0.3s ease; 19 } 20 21 body { 22 margin: 0; 23 min-height: 100vh; 24 display: flex; 25 justify-content: center; 26 align-items: center; 27 background-color: var(--color-primary); 28 color: var(--color-font); 29 } 30}

Step 3: Update your layout.tsx page

This is your parent component that wraps every page in your app. Consider it the root component and also a good place to place components that you want to be visible on every page such as a navbar or footer. For now we keep it simple an just want to bring in a main section which will contain or child components.

1import "./styles/globals.css"; 2 3export default function RootLayout({ 4 children, 5}: { 6 children: React.ReactNode; 7}) { 8 return ( 9 <html lang="en"> 10 <body> 11 <main>{children}</main> 12 </body> 13 </html> 14 ); 15} 16

Step 4: Update your app/page.tsx page

This is your website's index page, i.e. the landing page when someone visits your website for the first time.

Note about the src for the image component. You will need to place those files in the public folder and name them appropriately. This is temporary and we will set something up more dynamic later on.

1"use client"; 2 3import { useState } from "react"; 4import Switch from "react-switch"; 5 6import Image from "next/image"; 7import styles from "./page.module.css"; 8 9export default function Home() { 10 11 const [checked, setChecked] = useState(false); 12 const theme = checked ? "dark" : "light"; 13 14 const handleChange = (nextChecked: boolean) => { 15 setChecked(nextChecked); 16 }; 17 18 return ( 19 <div className={styles.pageContainer}> 20 <Image 21 src={theme === "light" ? "/sun.svg" : "/night.svg"} 22 alt="Theme illustration" 23 width={200} 24 height={200} 25 priority 26 /> 27 <h1>{theme === "light" ? "Light Theme" : "Dark Theme"}</h1> 28 <label> 29 <Switch 30 height={35} 31 handleDiameter={32} 32 width={75} 33 checked={checked} 34 onChange={handleChange} 35 boxShadow="0 1px 5px rgba(0, 0, 0, 0.6)" 36 /> 37 </label> 38 </div> 39 ); 40} 41

Don't forget to add your page.module.css file in your app folder to style your app/page.tsx file.

1@layer page { 2 .pageContainer { 3 display: flex; 4 flex-direction: column; 5 justify-content: center; 6 align-items: center; 7 gap: 1rem; 8 } 9}

Step 5: Time to test....hold onto your butts

1npm dev

Then open your browser and go to http://localhost:3000 and you should see your beautiful work of art, your basic theme switcher. But there is one problem as you'll notice. The background colour only covers a small rectangular space and not the entire page. Now the simple solution is to add these lines to your .pageContainer class in your page.module.css file:

1height: 100vh; 2width: 100vw;

But just like America's dependency on infinite growth, employee burnout and promoting war for oil, this is not sustainable in the long and not advisable due to it coming to bite you in the a** later. The problem is that you are only changing the colour in a child container, which means that if you want to change the color of the parent you would have to "lift the state up" to the parent. Ideally you want to be able to change the colour at the parent component and then have the colours be inherited in it's child components.

To do this, we will create a custom react hook which we can call on in any client component.

.......And this we will do in part 2.

Conclusion

Now you should have completed the basics of setting up a theme switch icon which allows you to change between two hard coded colours. This is a great start but I want to show you how to wow yourself and anyone looking at your code. So in Part 2, we are going to cover:

  • Create a mock API to dynamically change the colours for the theme as well as the photos and store this information in a CMS
  • How to update the colors in the css file without having to hardcode them.
  • Set the theme based on the browser's preference
  • Customise the icon and colour of the react-switch component

Find the full code here