This content originally appeared on Go Make Things and was authored by Go Make Things
Last week, we looked at how to create accessible and semantic color palettes as part of my deep dive into how I’m building Kelp, my UI library for people who love HTML.
Today, I wanted to show you how I’m implementing light and dark modes. Let’s dig in!
Base Colors
For this article, we’ll look at one of our semantic colors: --color-primary-*
.
All of the others work the same way, and focusing on just one will make this easier to explain and understand.
Our color palette currently includes the following CSS variables defined on the :root
element.
:where(:root) {
--color-primary-fill-muted: var(--color-blue-95);
--color-primary-fill-accent: var(--color-blue-90);
--color-primary-fill-vivid: var(--color-blue-50);
--color-primary-border-muted: var(--color-blue-90);
--color-primary-border-accent: var(--color-blue-80);
--color-primary-border-vivid: var(--color-blue-60);
--color-primary-on-muted: var(--color-blue-30);
--color-primary-on-accent: var(--color-blue-20);
--color-primary-on-vivid: white;
--color-primary-outline: var(--color-blue-50);
}
These are the default “light theme” styles.
Activating dark mode
To enable dark mode, we’ll use a .kelp-theme-dark
class.
I originally planned to use a data attribute—[data-kelp-theme="dark"]
—but because of CSS specificity and how the cascade is ordered, some light theme variables were sticking around even when dark mode was enabled.
In the same cascade layer as where the default colors are defined, I add the .kelp-theme-dark
class and reassign any applicable variables to new color values.
:where(:root) {
/* default colors */
}
.kelp-theme-dark {
--color-primary-fill-muted: var(--color-blue-20);
--color-primary-fill-accent: var(--color-blue-30);
--color-primary-border-muted: var(--color-blue-30);
--color-primary-border-accent: var(--color-blue-40);
--color-primary-on-muted: var(--color-blue-95);
--color-primary-on-accent: var(--color-blue-95);
--color-primary-outline: var(--color-blue-70);
}
From testing, I found that the *-vivid
colors looked great in light and dark mode, so I don’t change those.
I repeat this pattern with all of my semantic colors. I change some other colors, too.
For example, there are variables for body, text, and link colors. They all get updated in dark mode.
:where(:root) {
--color-background: white;
--color-text-normal: var(--color-gray-10);
--color-text-link: var(--color-blue-50);
--color-text-link-hover: var(--color-blue-40);
}
.kelp-theme-dark {
--color-background: var(--color-gray-10);
--color-text-normal: white;
--color-text-link: var(--color-blue-70);
--color-text-link-hover: var(--color-blue-80);
}
Light Mode Override
One other thing I wanted was the ability to activate the dark mode theme on a page, but override it to light mode in select elements or areas on the page.
Where I define my default light mode styles on the :root
element, I also define them on the .kelp-theme-light
class.
:where(:root),
.kelp-theme-light {
}
This means you can do stuff like this…
<body class="kelp-theme-dark">
<h1>Hello world!</h1>
<p>This section is in dark mode.</p>
<button>Say Hi</button>
<div class="kelp-theme-light">
And everything in here is styled in light mode.
</div>
</body>
Switching Color Modes
In the past, I’ve used CSS and the prefers-color-scheme: dark
media query to automatically toggle between light and dark mode.
@media (prefers-color-scheme: dark) {
/* Dark mode CSS variables */
}
That totally works, but some folks implementing Kelp may want to enable only light mode or only dark mode.
And some may want to include a toggle so that users can choose between light mode, dark mode, or whatever their OS settings currently are.
So, I decided to write a little JavaScript snippet that automatically detects the user’s system preferences and sets the correct class on the html
element. It also automatically updates the class if the user changes their OS settings.
/**
* Automatically toggle dark mode based on users prefers-color-scheme OS setting
*/
(() => {
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
document.documentElement.classList.toggle('kelp-theme-dark', prefersDarkMode.matches);
prefersDarkMode.addEventListener('change', (event) => {
document.documentElement.classList.toggle('kelp-theme-dark', event.matches);
});
})();
One of the features I’ll be adding to Kelp is a light/dark mode toggle for people who want to control it manually instead (which would replace this snippet).
Dig into Kelp
Kelp officially launched over the weekend!
The source code is fully available and unminified, and the docs are rapidly getting built out. Over the next few weeks, I’ll be releasing various Web Components to add more interactivity to the library, and tools to make customizing it even easier.
If you have any questions, feature requests, or other comments, reach out and let me know!
Like this? A Lean Web Club membership is the best way to support my work and help me create more free content.
This content originally appeared on Go Make Things and was authored by Go Make Things

Go Make Things | Sciencx (2025-06-23T14:30:00+00:00) How to create light and dark color modes with CSS. Retrieved from https://www.scien.cx/2025/06/23/how-to-create-light-and-dark-color-modes-with-css/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.