You may think the title of this article is hyperbole, but I assure you what is coming for managing "dark mode" in web components is about to get awesome. Before we get into the solutions, let's talk about the problem.
The Problem
When working with light and dark mode themes today we typically have two approaches to solving the problem - using media queries and using a theme switcher/toggle.
Using Media Queries
The first is with the prefers-color-scheme
media query and CSS custom properties. Colors and styles are defined one way for "light mode" and again within a media query for "dark mode".
This method allows us to provide styles based on the user's system preferences, but leads to large CSS files and makes it difficult to manage the theme globally or at a component level (for example, if we wanted to create an "inverted" nav bar) because it's difficult to force the color scheme to change.
NOTE: In addition to the many benefits of CSS custom properties, it is important to note that they are inheritable across shadow roots, which makes them fantastic tools for managing design tokens in a web component library.
Using a Theme Switcher
Another scenario is when sites provide a theme toggle so users can choose between light and dark modes, but also allow it to fall back to user preferences if one isn't selected. This usually requires defining styles scoped to "light-mode" and "dark-mode" class names and some JavaScript to check the user's system preferences to set the user hasn't been specified.
This can introduce FOWC - Flash of Wrongly-styled Content (yes, I just made that up) - while the browser loads and parses the JavaScript. It also makes it very inconvenient to understand what "mode" our project is in within the shadow DOM.
Enter light-dark()
CSS Function
light-dark()
is a new CSS color function that allows us to define light and dark mode colors in a single property without needing to redefine our properties in a media query or alternative class name. With this new function, we can simplify the implementation by not having to define the style in two different places.
This example provides the same functionality as the first example with the media query, but with significantly less code and is much easier to maintain.
If we use a design token system, this can be used with CSS custom properties (variables) to create other CSS custom properties.
--button-bg-color: light-dark(var(--light-color), var(--dark-color));
Using color-scheme
If we want to add the theme switcher capability, we can do so with just a few lines of CSS. The CSS color-scheme
property allows us to dictate to the browser which color scheme to use. This is nice because it not only uses the light and dark colors defined in the light-dark()
function but also the browser's native light and dark mode colors.
Composing Web Components
Let's add a web component into the mix and see what happens. In this example, the web component requires different colors than the global button colors we originally defined.
One of the best parts about this is that the color-scheme
value is inheritable across the shadow root, meaning the code in our shadow DOM can be context-aware of the color scheme!
Creating Themable Components
Not only can the components be aware of the color scheme, but we can also dictate it based on the component's API.
Considerations
At the time of writing this article, the light-dark()
function is in the beta release of Safari. It also looks like the MDN browser compatibility info and caniuse.com are not accurate. These work in both Edge and Opera.
Conclusion
With these new CSS features, our ability to style content in and outside our web components will significantly improve. We can get more features than we have today with much less code that is more performant and compatible with the native browser APIs.
Top comments (5)
github.com/mdn/browser-compat-data...
Nice! Thank you!
Well written, thanks for taking the time to present this.
I'm definitely liking the structure/formatting for
light-dark
, but support for it isn't great. Yet.Unfortunately, that's not accurate (check out the "Considerations" section in this article). We're just waiting on Safari and it's already on its way.