DEV Community

Alexandru Ene
Alexandru Ene

Posted on

Implementing Light/Dark Theme - My Struggles and Tips

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.

  1. 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.

  2. 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.

  3. 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

  1. 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;
    }
    
  2. Instead of targeting body or specific elements, I added the theme class directly on the html 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');
      }
    };
    
  3. 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)

Collapse
 
csm18 profile image
csm

I recently worked on a simple pet project,for which after a lot of struggle,I got this:


:root {
  --neon-blue: #1e90ff;
  --neon-black: #0d0d0d;
  --neon-green: #00ff85;
  --soft-gray: #cccccc;
}

body[theme="dark"] {
  --home-bg: var(--neon-black);
  --nav-link-color: var(--neon-blue);
  --nav-bg: var(--neon-black);
  --hero-bg: black;
  --hero-phrase: var(--soft-gray);
  --hero-btn-color: var(--neon-green);
  --hero-btn-bg: var(--neon-black);
}

body[theme="light"] {
  --home-bg: white;
  --nav-link-color: var(--neon-blue);
  --nav-bg: white;
  --hero-bg: white;
  --hero-phrase: var(--soft-gray);
  --hero-btn-color: var(--neon-green);
  --hero-btn-bg: white;
}

Enter fullscreen mode Exit fullscreen mode
theme_toggle() {
    const body = document.body;
    const currentTheme = body.getAttribute('theme');


    if (currentTheme == 'dark') {
      body.setAttribute('theme', 'light');
    } else {
      body.setAttribute('theme', 'dark');
    }
  }

Enter fullscreen mode Exit fullscreen mode

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!

Collapse
 
uzzy412_73 profile image
Alexandru Ene

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...

Collapse
 
cristea_theodora_6200140b profile image
Theodora Cristea • Edited

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🤗🥰

Collapse
 
kito_yaroshi profile image
kito yaroshi

Interesting now

Collapse
 
uzzy412_73 profile image
Alexandru Ene

Thank you!

Collapse
 
uzzy412_73 profile image
Alexandru Ene

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.

Collapse
 
parag_nandy_roy profile image
Parag Nandy Roy

It’s so relatable ...dark mode sounds easy until you’re knee-deep in CSS chaos...

Collapse
 
uzzy412_73 profile image
Alexandru Ene

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.