A modern dark mode - part one

I’ve wanted to implement dark mode for quite a while now. But, I just didn’t want to ship a part of it. I had to include a perfect dark theme, together with the perfect accessible toggle. I might be a perfectionist.

However, two things have happened last month that changed my perspective on this approach.

The first thing was a conversation with a colleague who has a rare vision impairment called Achromatopsia , which includes multiple conditions, but primarily it means a total colour blindness and that looking at bright lights causes pain. Through talking with him about it, I learned that dark mode greatly improves his experience on the web.

The second thing was a great article about progress over perfection , published by Meryl Evans. In the article she writes about the importance of taking small steps to improve accessibility, instead of a doing a big bang.

Knowing that dark mode is such a hard requirement for some, I can’t allow myself to wait to get everything perfect.

So, should we go dark mode all the way?

That’s what I thought at first. Because if you search for “dark mode”, you’ll probably see a list of advantages about it.

But after reading about it more and more, I found out that a lot of people have a vision condition called Astigmatism that doesn’t go well with dark mode. What happens with Astigmatism is that white text on a black background will create a visual fuzzing efect called halation . In short, this causes text to get blurry and this causes terrible headaches.

Now we know that only having a light or a dark theme doesn’t work, we have to provide both themes and let our users decide.

A modern and flexible light and a dark theme implementation

What I really like about CSS variables , is that they can inherit from other CSS variables. This allows us to create “functional” CSS variables with a clear name and purpose, which keeps our CSS clean. Here, I’ll show you!

@import "./colors.css";

:root {
  color-scheme: dark light; 
  --background-base-color: var(--color-gray-50);
  --text-heading-color: var(--color-gray-900);
  --text-body-color: var(--color-gray-700);

@media (prefers-color-scheme: dark) {
  :root {
    --background-base-color: var(--color-gray-900); 
    --text-heading-color: var(--color-gray-200);
    --text-body-color: var(--color-gray-400);

body {
  background-color: var(--background-base-color);

h1, h2, h3, h4, h5, h6 {
  color: var(--text-heading-color);

p {
  color: var(--text-body-color);

To summarise what’s happening here:

There’s one last thing that I want to mention. And that’s that our themes are declared only inside CSS. While usually this isn’t a problem, but in this case it could lead to a bad user experience.

The flash of inaccurate theme, or FART

What happens when the user visits our website and can’t download the CSS in time? The flash of inaccurate color theme — or FART as Chris Coyier coined it — will happen.

Imagine that you’re browsing the web, in a dark environment, and suddenly a bright light flashes you for a split second. That’s what the flash of inaccurate color theme means.

Thankfully, it’s easy to solve. We only have to move the color-scheme property out of CSS and use the meta tag of the same name:

  <meta name="color-scheme" content="dark light">

There, problem solved! Now we don’t flash our users with a bright white screen in the middle of the night.

Wait, what about a toggle?

Well, as I mentioned before, I believe in the progress over perfection approach. So I haven’t implemented the toggle part yet, but I’m working on it. You can definitely expect an article on this subject in the future.