DEV Community

Cover image for I Thought Dark Mode Was a 30-Minute Task. It Turned Into a Full Refactor
Marsha Teo
Marsha Teo

Posted on • Originally published at marshateo.com

I Thought Dark Mode Was a 30-Minute Task. It Turned Into a Full Refactor

I thought adding dark mode would take 30 minutes. It broke my website.


Problem 1: Hardcoded colours

I didn’t even use that many colours. How hard could it be? Turns out, very.

My colours were hardcoded directly into Tailwind utility classes. A heading had a specific hex value, and a paragraph had another. To change the theme, I would have had to update each of these individually. No, thank you.

The fix

I introduced CSS variables and defined colours by role, not value.

My first attempt had variables like hover-dark and hover-darker. That worked until I tried to invert the theme for dark mode. What would hover-darker even mean in dark mode?

Instead of asking "What color should this element be?", I had to ask "What role does this color play?"

So I switched to variable names that were semantic, rather than literal:

  • --text-primary
  • --text-secondary
  • --background
  • --border

With this, a heading wasn’t “black” anymore. It was text-primary. A background wasn’t “white”. It was background-primary.

At this point, I thought I was mostly done. I wasn’t even close.


Problem 2: Dark mode is not white on black

I thought dark mode meant white text on a black background. It looked terrible. Who would've thought that having white text on black would feel so... bright? It was harsh, hard to read, and everything started blending together - almost like I had suddenly developed astigmatism.

The fix

It turns out dark mode isn't really black and white. It's shades of grey. Instead of white, I used a light grey and text was miraculously legible again. For secondary text, even lighter grey worked perfectly.

Pushing contrast to the extreme with white text on black backgrounds was a disaster. Tuning that contrast and making it proportionate and layered made things way more comfortable.

White text on black background


White text on black was too much contrast on my screen. I felt it in my eyes.

Grey text on black background



Subtle change to reduce contrast, making it easier to read over a longer period of time

And that's all my problems solved, said no one ever.


Problem 3: Everything breaks differently

Even after fixing colours, things still looked off. Different parts of the UI broke in different ways:

Problem 3.1: Typography (Tailwind)

Tailwind’s typography plugin (prose) worked great in light mode. But once I introduced my own variables, the defaults started conflicting with my system. Some styles updated, others didn’t. Fixing one thing broke another. The abstraction broke down, and the complexity I’d tried to hide came rushing back.

The fix

I explicitly mapped Tailwind’s typography variables to my own. Instead of relying on defaults, I treated typography as part of my system.

Once everything pointed back to the same set of variables, things became predictable again.


Problem 3.2: Code Syntax Highlighting

I use a lot of code snippets, especially in my JavaScript event loop article series. For syntax highlighting, I had used Github's light theme for my light mode. Too bad it was impossible to read in dark mode. So I switched to Github Dark CSS - only for it to look off in light mode.

The fix

For a while, I assumed I had to pick one. Eventually, I realised the obvious solution: use both Github and Github Dark CSS and switch dynamically based on the mode. It sounds pretty obvious now, but at the time, I genuinely thought I had to choose.

Github CSS in Dark Mode



Github's light theme in dark mode was impossible to read

Github Dark CSS in Light Mode



Github's dark theme in light mode looked washed out


Problem 3.3: Images

Images introduced a different kind of problem. Some worked fine. Others didn’t translate at all.

My hero image is of a sunrise. From the start, I imagined using a sunset version for dark mode. Did I create dark mode just so that I can use this image? Maybe.

Thankfully, this was easily implemented by including both images and switching between them based on the mode.

But my SVG diagrams were harder. I tried making their colors dynamic using CSS variables but it didn’t work reliably.

The fix

Instead of forcing everything to be dynamic, I created two versions of each diagram, one for each mode. It felt less elegant at first, but it worked better. Not everything should be dynamically styled.

SVG Diagrams Not Adjusted for Dark Mode



Diagrams designed for light mode don’t translate automatically.


Problem 4: The flash

After fixing everything, I refreshed the page. A flash of light mode appeared before it switched to dark. It was subtle, but definitely there. And yes, the temptation to pretend that didn't happen was definitely there too.

The browser was rendering before the correct theme was applied. By the time JavaScript the correct theme was set, the browser had already painted the wrong one.

Flash of light mode when refreshing in dark mode



The flash: light mode renders before dark mode is applied

The fix

The theme needed to be determined before rendering. Moving the theme logic earlier removed the flash entirely. It was a small change technically, but it had a big impact on how the site felt.


The real lesson

I thought I was adding a feature: a toggle button and a visual enhancement that sits on top of everything else.

But dark mode didn’t sit on top of my UI. It ran through it and every part of the system had to agree. None of the above was individually difficult. But together, they revealed that dark mode was a system and one that needs to be designed intentionally.


If you’re implementing dark mode

A few things I wish I knew earlier:

  • Define colors by role, not value
  • Avoid extreme contrast
  • Treat typography as part of your system
  • Don’t force everything to be dynamic
  • Handle theme selection before render

If you’re curious, the full implementation and visuals are on my site. This article was also originally published there.

Top comments (0)