Let There Be light-dark()
In the beginning, there was light mode, but lo! After a time, the retinas of webizens did cry out, yearning for reprieve.
Already have some familiarity with CSS and accessibility? You can skip ahead to the demo below.
From the Light (Mode), Darkness
Among the more popular trends in UI/UX in the last few years is the build-out of dark modes to create a more robust and accessible online experience. Think you're unfamiliar with the idea of dark mode? That may not be as true as you think.
In the upper-right hand corner of this page, you'll see either a sun or moon-and-stars icon. Clicking or tapping that icon will fundamentally change the site's appearance, shifting it to either the more traditional white-out background with black text or to an experience that is comprised of light text against a dark background. The latter, which I made the default experience as part of my recent site migration, is what is known as dark mode.
Dark Mode as a Matter of Accessibility
One thing you may notice in shifting between light and dark mode is, quite simply, how it feels. If you don't believe me, try a couple of toggles for yourself now. You may notice that, after a few seconds of engaging with the site in light mode, your eyes strain to endure the blast of light.
Now imagine staring at light-mode-only interfaces for forty hours per week (longer if you count all that time staring at your phone while doing, well, literally anything). Oof. Ouch. Those poor, poor eyes.
Even if you're not feeling the strain personally, you don't have to take your own experience (or my word) as gospel. The Bureau of Internet Accessibility, for example, cites that dark mode "has substantial benefits," and Perkins School for the Blind adds that "[dark mode] can be especially beneficial for users with low vision and light sensitivity."
It would seem, then, that dark mode shouldn't be an alternative, but rather the standard. Why provide a mode toggle at all?
Do Not Go Brusquely Into that Good Night
As it turns out, despite its benefits, dark mode can cause trouble for some users, including those with dyslexia, which could affect as much as 17% of the U.S. population according to the University of Michigan's Dyslexia Help Center.
It seems, then, that offering users the option to choose between a light mode and a dark mode is the way to go, but how does one add a dark mode to their site?
Let There Be light-dark()
Before we explore light-dark()
—one of the latest ways to add a secondary mode to one's site—I should note that light-dark()
is not how I ultimately chose to implement dark mode via the toggle at the top of the page. We'll explore the reasons why I opted for a distinct approach later, but light-dark()
does offer a light-weight, CSS-native approach to making dark mode available to your site's users, so it's worth knowing how to take advantage of it.
Alright, all of that said, I think we're ready for the how-to.
The What and How-To of It All
light-dark() is a CSS function that allows you to easily implement light and dark themes on your site. It requires no JavaScript, no third-party packages, and no additional HTML elements. In other words, it can be implemented only with vanilla CSS, making it, as I mentioned earlier, about as light-weight as light-weight can be.
Spin Up a New App
For the purposes of this exploration, let's spin up a new React app called theme-machine with Vite.
Note: You may be prompted to install Vite when running the first command. If you are, do so.
npm create vite@latest theme-machine -- --template react
code theme-machine
Once you've got your app open in your IDE, be sure to install the necessary dependencies before running the app.
npm i && npm run dev
Now head to http://localhost:5173
in your browser to see the Vite template in action. If you're an acolyte of the darkness like me—by which I mean you have already set your OS to prefer dark mode—you'll notice that the Vite template is already in dark mode.
If the Vite React template is already preference-responsive out of the box, we're done, right? Ha! Only if you plan to turn your back on light-dark()
!
As it turns out, the Vite React template achieves a similar effect to light-dark()
by implementing the prefers-color-scheme
media query instead. This can be seen in portions of the index.css
file the template generates below.
:root {
...
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
}
...
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
We could call it quits here, yes, and say we implemented both a light mode and a dark mode, but... we haven't yet done it with light-dark()
, which, you know, is the entire point of the post, so let's pivot away from prefers-color-scheme
and instead, at long last, implement light-dark()
.
Turning On (or Off!) the Lights: Implementing light-dark()
To truly make this app our own, let's visit src/App.jsx
in our theme-machine
app and replace it with the following code.
import './App.css'
function App() {
return <h1>It's me, your pal light-dark()</h1>
}
export default App
Next, replace the contents of src/index.css
with the following code.
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
color-scheme: light dark;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
Alright, let's take a look at our browser and inspect our work so far.
We removed the prefers-color-scheme
media query, yes, but if the app still appears in dark mode for you—which it should if that's the preference you've set at the OS level—that's to be expected at this juncture.
How? Why? Let's explore.
It turns out, setting color-scheme: light dark
at the :root
level is enough to trigger some responsive behavior in the browser, even if you haven't set additional color-related styles of your own. This is achieved by the browser applying its user-agent styles to the :root
pseudo-class, which in turn apply to the rest of the page.
But we're not here to rely on the browser's default styles, are we? No, we're here to implement light-dark()
! To do this, let's add some color
and background-color
styles to the body
element styles in App.css
, at long last making use of light-dark()
.
body {
/* BEHOLD, LIGHT-DARK() AT LAST */
color: light-dark(midnightblue, aliceblue);
background-color: light-dark(aliceblue, midnightblue);
/* STYLES THAT WERE HERE BEFORE */
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
Color Me (Un)Surprised
light-dark()
accepts more than named colors as arguments! You can use hex codes, RGB values, HSL values, or even custom properties.
In your browser, you should now see one of the following, depending on your OS's color scheme preference.
How It Works and Testing Both Themes
As one might surmise from the implementation above, light-dark()
takes two arguments, with the first of these accounting for the color that should be used in light mode, and the second representing the color to display in dark mode. The function then returns the appropriate color based on the user's preference. In the case of the body
element in our app, the text color is midnightblue
in light mode and aliceblue
in dark mode, while the background color is aliceblue
in light mode and midnightblue
in dark mode.
Fun Fact
light-dark()
can be used with any CSS property that accepts a color value, not just color
and background-color
!
To test both themes, you could, yes, change your operating system's preference (or, if you embrace dynamic color scheme preference, wait for the sun to go up or come down, depending on the time of day), but that's a bit of a hassle. Instead, you can use the browser's developer tools to simulate each theme.
In Chrome, this can be achieved by opening your dev tools, clicking the three dots in the top right corner, selecting "More tools," then "Rendering," and finally selecting the color scheme you'd like to simulate in the "emulate CSS media feature prefers-color-scheme" dropdown. You should see the page's styles update to reflect the selected color scheme the moment you select the option you'd like to simulate from the dropdown.
Congratulations and Cautions
You did it! You've successfully implemented light-dark()
and know how to test it out for yourself.
That said, maybe don't start slapping it in all of your apps quite yet.
Why? Because as of the date of publication for this post, light-dark()
has only just become available to all of the most popular browsers. MDN indicates it's available in all of the below browsers, though only in the latest versions of them.

light-dark()
is available in most browsers, though only in some of the latest versions of them.Beyond browser limitations, it should be noted that light-dark()
is also always tied to a user's color scheme preference, which means there's no innate way to let users toggle between light and dark mode when they visit your site. And, as we explored above, providing users with a toggle is important for any developer who wants to maximize their site's accessibility.
This is among the reasons I ultimately chose to forego the use of light-dark()
when rebuilding this site. Providing users with the ability to toggle between light mode and dark mode was an important feature for me to provide, personally, but if you're looking to provide some baseline responsiveness with respect to your site's color scheme, light-dark()
is, as stated previously, a light-weight, CSS-native approach that will get you to MVP expeditiously.
Just keep in mind that the M in MVP stands for minimum viable product, and I like to think that, whenever possible, we should strive for a little more than minimum, whether we're in the dark or the light.