Introduction
Recently I wanted to implement a Light/Dark theme for a website I am working on currently. At first I thought it couldn't be that hard, right? I told myself I just change some colors and I am good to go. But not long after I faced the real challenge, because it was way more trickier than it looked.
So in this post I would like to talk about the struggles I faced and the solutions I found along the way. Hopefully, I will save you some headaches.
What I Tried & What I Struggled With
When I started working on my website project, I thought of using dark colors for the design. I did my research, checked tons of webpages to see what I am missing and what I should implement. So I noticed I don't have a toggle button to change to a light theme.
Specificity & Poor Planning. So I started mixing some media queries prefers-color-scheme, because I wanted my website to automatically detect the user system preferences, and then I worked on the button itself. And here I ran into css specificity problems, because my css file was totally a mess. The body element had a .dark-theme by default, because that was my design in the first place. I played a little with developper tools on Chrome and changed user preference to light. That class on the body element was somehow overwriting the media query and my website turned up half light and half dark, because of specificity issues. There was no user preference theme at all.
Hardcoded Color Values & Poor Custom Variables. When I tried to change colors from dark to light, I noticed there is a lot of work to be done. I did actually got tired of rewriting the selectors just to change some colors. It felt like I was working on two different websites all at once. I did use a few poor custom variables and try to change them to their opposite values when the body element had .light-theme class. But I noticed some problems. The contrast was totally off. A color was working nice on certain backgrounds, but failed miserably on others. And the hardcoded values? Well, I have to search them and turn them into css custom properties. But I still failed.
Repeating Code. I ended up writing a lot of repetitive code just to make it work. I had body.light-theme and body.dark-theme selectors and media (prefers-color-scheme: light) and they just repeated the same selectors, but with just a few values changed here and there. It was not DRY at all, hard to read and too much of struggle.
How I Solved It
-
I had to clean everything up. Instead of hardcoded colors everywhere in my CSS, I carefully defined all of them as custom properties in the
:root
selector. It made everything easier to override and manage.
:root { --bg-color: #ffffff; --text-color: #111111; --accent-color: #0077ff; } html.dark-theme { --bg-color: #121212; --text-color: #eeeeee; --accent-color: #4dabf7; }
-
Instead of targeting
body
or specific elements, I added the theme class directly on thehtml
tag using React. This helped avoid specificity issues and made the cascade cleaner.
const changeTheme = (e: MouseEvent) => { const target = e.currentTarget as HTMLElement; const html = document.documentElement; if (!target) return; if (target.dataset.theme === 'os') { html.classList.remove('light-theme', 'dark-theme'); } else if (target.dataset.theme === 'light') { html.classList.remove('dark-theme'); html.classList.add('light-theme'); } else { html.classList.remove('light-theme'); html.classList.add('dark-theme'); } };
I later implemented a button that lets users choose between light mode, dark mode, and system preference.
This approach made my code cleaner, more maintainable, and much easier to extend later.
What I Learned
Custom properties are so worth it. Taking that extra time to define them properly will defend me from starting over again, rewriting and retrying.
A clean structure matters. Most of my issues came from messy, not organized CSS. Once I cleaned that up, everything else got easier.
I don't need any frameworks to do it the right way. I am not planning using Tailwind for this project. But I might look into it a bit later.
Final Words
Implementing a light/dark theme may seem easy at first, but it can become a challenge if your CSS isn’t well organized or if you don’t plan carefully. Using CSS custom properties and managing theme classes at the right DOM level made all the difference for me — it simplified maintenance and solved many headaches around specificity and repetition. If you’re starting this journey, take the time to structure your styles cleanly; it will save you hours of frustration later.
Now I am curious, how would you implement it without so much trouble? Let's discuss pros and cons!
I hope sharing my struggles and solutions helps you! Happy coding and see you in the next one!
Top comments (8)
I recently worked on a simple pet project,for which after a lot of struggle,I got this:
I just change the theme attribute value on body using js and we get other theme.
In this way, all the colors I use are there in root.
And, to change a theme color like Nav-bar bg, I just can change the value of variable with the name in light theme or dark theme.Thats it!
Nice! After all the struggle, you get to the conclusion: it is not impossibly difficult, if you put in the time to correctly set your custom variables. Should have known that, but I was lazy...
Hey Alex!👋🏻 You’ve picked a very interesting and useful topic! I’m currently working on a project where I need to implement a light-dark theme, and your idea of applying custom classes directly on html is really helpful.
Initially, I was thinking of adding them on body, but I guess I would have run into the same issues as you did...
Regarding custom variables, I organize and name them using this logic:
1.Based on their purpose:
background-color, color, hover, etc.
2. I create versioned variables, for example:
--bgc-10: ...;
--bgc-11: ...;
--bgc-12: ...;
If my project’s main color is red and the text color is white, I define them like this:
--bgc-10: rgb(255, 0, 0);
--color-10: rgb(255, 255, 255);
Here, “10” stands for the primary version of the background and text color. If I need variations, I create --bgc-11, --bgc-12, etc. Instead of numbers, I could also use letters (--bgc-a, --bgc-b), in case you need more color variations. This way, the variable naming doesn’t interfere with the light or dark theme structure.
I’d also suggest saving the selected theme in localStorage so that the user’s preference is preserved even after reloading the page.
Thanks for sharing this post👌🏻and I hope my tips help you as much as yours helped me! Happy coding🤗🥰
Interesting now
Thank you!
Thank you for taking time to reply! Your way of organizing css variables look like a reasonable one. As for other aspects, saving it to localStorage could make a small UI/UX improvement.
It’s so relatable ...dark mode sounds easy until you’re knee-deep in CSS chaos...
Having to add lots of custom properties and doing it right... Not even know how to name them at some point. Yeah, that's chaos.