Color Modes & Themes #3807
alanbsmith
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Color Modes & Themes
a general introduction and suggested implementation
Table of Contents
Prior Art
Much of what is described below is heavily informed by GitHub’s theming functionality, which I consider to be best-in-class. They support system, light, and dark modes. They also currently support seven color themes, four of which are tailored for colorblindness, and they provide a high-contrast toggle for light and dark modes. If you have a GitHub account, and you’d like to investigate further, you can control them in your settings.
Overview
This guide provides a basic understanding of color modes and themes, how they work together, and how they are applied. This is intended for any technologist, so some information may seem introductory. It also covers details on a web implementation to ground the material a bit further. If you’re not interested in reading code snippets, you should still be able to benefit from the higher-level notes. If you prefer hands-on learning by interacting with an interface and pushing buttons, you can poke around at this Codepen prototype.
While color modes and themes are largely about aesthetic and personalization, the most important goals are to provide a uniform experience that is:
Color Modes vs Themes
Before jumping into the details, It’s worth taking a moment to describe the difference between “color modes” and “themes.” While the terms are often used interchangeably, they have important distinctions between them.
A color mode is a modality that allows users to control which themes are available and applied to their UI. Modes can be activated and deactivated, and there can only be one active color mode. For example, it is not possible to have both light and dark modes active at the same time.
A color theme defines a specific color palette or collection of palettes. It sets variables and overrides others to apply the theme to the interface. Themes can be independent of color modes, but they are often tailored to reflect the particular active mode. For example, a dark theme defines a set of variables that are rendered when activating dark mode. Themes can serve a variety of purposes from aesthetic, to accessibility, to brand alignment. They can be layered, and often multiple themes are applied at once. Their layer structure informs how these themes are applied to the interface.
Color Modes
Color modes allow users to control how themes are expressed. The three most common color modes are dark, light, and system (sometimes called “automatic”).
Web Implementation
The best method for toggling color modes is through a single, top-level variable. This creates a single source of truth that is:
A common best practice is to set a data attribute on the root
<html>element. In the code example below, we’re usingdata-color-mode.These modes can then be referenced in CSS. Light and dark modes largely work the same. The color-scheme CSS property tells the browser how to align styles with the current active mode. Note that there are no particular CSS variables being defined for any mode – those are defined in the color themes.
System mode works a bit differently in CSS, as it requires a media query, prefers-color-scheme, to reflect the operating system’s current active theme.
Detecting the current color mode in JavaScript is also pretty straightforward. This would probably look different in React, but this is how we could do it with plain JavaScript.
We can also toggle the current color mode with plain JavaScript as well.
Color Themes
As stated above, a color theme defines a specific color palette or collection of palettes that are applied to the UI. These themes are applied to the interface when a particular mode is activated and the theme is selected.
You might be wondering: Why don’t we allow the color mode to define the color palettes instead of using themes? We certainly could! From an implementation standpoint, there’s no technical reason why we couldn’t go that route. However, it comes with a major tradeoff: If you ever want to have multiple light or dark palettes, and your logic is tied to color modes instead of themes, you have quite a lot of untangling to do. It’s quite common for interfaces to have a dark and soft dark themes to accommodate different aesthetics and user preferences. Separating modes and themes allows us to keep that flexibility at little-to-no complexity cost.
Below, we’ll look at each theme layer and discuss how they work together.
Theme Layers
In the initial overview, we mentioned that themes can be layered. The number of layers and how they override is dependent on the application’s product requirements. Most modern interfaces have at least a single base layer to toggle aesthetic color themes such as light, dark, and dimmed or soft dark. Other interfaces with more advanced theming provide a secondary theme layer which is generally used for enhanced accessibility. Still other interfaces provide a separate theme layer for private labeling or branding to align it with the tenant’s brand, such as Workday, Nike, and Apple.
Base Layer Themes
Base layer color themes, such as light, dark, and dimmed provide the overall foundation for the interface. They touch every part of our interfaces: surfaces, type, icons, buttons, and inputs. They are the primer coat to all secondary and tertiary themes that are layered on top.
It’s important to consider how these themes interact with their corresponding color modes. While the themes define variables used in their palettes, they are not applied until their associated mode is active. To provide multiple themes for any given mode, we need a separate mechanism to set and update them in code.
Web Implementation
To implement this in HTML, we’ll use two separate data attributes,
data-color-dark-themeanddata-color-light-themeon the root<html>element. This allows us to always track which theme should be rendered whether thedata-color-modeis toggled todark,light, orauto.To support this in CSS, we’ll need to update our selectors to apply these base layer themes and define our CSS variables. This is pretty straight-forward for light and dark modes, but we’ll need to use the media query to
prefers-color-schemefor system (auto) mode.We’ll discuss this further in the Accessibility Layer Themes section, but one nice quality about these color theme selectors is they use
*which fuzzy matches the values. This allows us to combine and layer themes using the same data attribute.Toggling between these themes with JavaScript follows a similar pattern to our mode implementation. Again, this would likely look different as a React implementation, but this is a straightforward approach for plain JavaScript.
Accessibility Layer Themes
Accessibility themes are layered on top of the base themes. They generally will not be as expansive as our base layer and will instead target specific variables to update. For example, a protanopia theme might only need to update red color palettes. Or a high-contrast theme might only update borders, typography, and iconography. It’s also worth noting that we will need a separate expression of these themes for light and dark modes.
Web Implementation
To implement these layers in HTML, we can follow our existing data attribute pattern. You can see from the values how we’re layering on our accessible themes using the same
data-color-dark-themeanddata-color-light-themeattributes. This user has set their dark theme to beDark + Protanopia + High Contrastand their light theme toLight + Protanopia.That level of customization is really helpful for users with different visual capabilities.
Similarly, in CSS, we can follow the pattern established in our previous implementation by modifying the color theme data attributes. Again, because we’re using the
*fuzzy match selectors, we don’t need to have every combination of selectors listed out – only the ones we need to explicitly define.And finally, to toggle with JavaScript, we can reuse our existing
setColorThemefunction.Brand Layer Themes
Tenant brand themes create another level of complexity because while we can know the default brand (Workday) ahead of time, we don’t necessarily know the name of the current tenant brand ahead of time. This means we need to control these styles separately and inject them when the application renders.
Brands will need to have separate themes for light and dark modes. That is perhaps something we could adjust programmatically, but it might also be something brands want to configure on their own.
Web Implementation
To toggle brand-specific theming in HTML, we’ll need to create yet another data attribute that we can inject into the DOM.
Our CSS would also follow a similar pattern as our other themes. Again, because we don’t know these themes ahead of time, these styles would need to be created and injected dynamically.
We might need a new JavaScript function to handle toggling the brand theme, but it would look similar to what we’ve done previously.
Conclusion
Color modes and themes work together to create flexible, personalized, and inclusive user interfaces. By separating the concerns of modes (light, dark, and system) from themes (base, accessibility, and brand layers), we can build systems that are both composable and maintainable — making it easy to add new palettes or override specific variables without untangling tightly coupled logic. A consistent implementation pattern using HTML data attributes, CSS custom properties, and simple JavaScript utilities provides a reliable single source of truth that scales gracefully across every layer of theming. When done well, this system ensures every user — regardless of visual ability, preference, or the brand they're interacting with — has a coherent and accessible experience.
Beta Was this translation helpful? Give feedback.
All reactions