color-scheme

Avatar of Geoff Graham
Geoff Graham on

You know how we have “dark mode” and “light mode” these days? Browsers also have “dark” and “light” color schemes baked into their default styles. The CSS color-scheme property lets the browser use (or choose) to display certain elements with its dark or light default styling.

:root {
  color-scheme: light dark;
}

The color-scheme property is defined in the CSS Color Adjustment Module Level 1 specification, where it is called the “Opting Into a Preferred Color Scheme” property.

That’s a great name for it because setting it enables the browser’s light and dark color schemes to take effect when it recognizes a user’s system preferences. If the user prefers light, they get the browser’s light color scheme. Prefer dark? They get the dark color scheme instead.

Think of it like providing the browser with a hint about the primary color of a page. If the primary color is light — such as a white background — then it’s best if an element’s default color has a darker contrast. If the primary color is dark, then the elements are better off with a lighter appearance.

And once the browser gets the hint, it applies its corresponding color-scheme from its stylesheet automatically without us having to write additional styles or media queries.

The CSS Color Adjustment Module Level 1 specification is currently in Editor’s Draft status at the time of writing. That means the feature could change between now and when the specification becomes a Candidate Recommendation. Browsers may implement the feature between then and now, but the feature is still considered experimental.

Syntax

color-scheme: normal | [ light | dark | <custom-ident> ]+ && only?
  • Initial value: normal
  • Applies to: all elements and texts
  • Inherited: yes
  • Percentages: n/a
  • Computed value: As specified
  • Animation type: discrete

The color-scheme CSS property can be applied to the :root element so it is inherited globally, or it can be set on an individual element.

Values

/* Keyword values */
color-scheme: normal;
color-scheme: light;
color-scheme: dark;
color-scheme: light dark;
color-scheme: dark light;
color-scheme: only light;
color-scheme: only dark;

/* Global values */
color-scheme: inherit;
color-scheme: initial;
color-scheme: revert;
color-scheme: revert-layer;
color-scheme: unset;

normal

This value prevents an element from opting elements into the operating system’s light and dark color schemes. Instead, the browser’s default color scheme is used.

This can be used to reset an element’s inherited color scheme. Say you set color-scheme on the :root element:

:root {
  color-scheme: dark;
}

All elements inherit that style. If we want a particular element to opt out of that style, that’s where normal relegates things back to the browser defaults.

:root {
  color-scheme: dark;
}

.some-element {
  color-scheme: normal;
}

light

This value opts elements into the operating system’s light color scheme.

dark

This value opts elements into the operating system’s dark color scheme.

only

This value is used alongside either the light or dark value:

:root {
  color-scheme: only light;
}

When we do that, we’re telling the browser to opt an element into only the light color scheme, or only the dark. So, if we set color-scheme: only light on an element, that element can receive the operating system’s light color scheme, but cannot use its dark color scheme… and vice versa.

Opting into light and dark color schemes at the same time

We can totally do that:

/* Light and dark color schemes are supported,
   but `light` is my preferred option. */
:root {
  color-scheme: light dark;
}

Notice that the order matters. So, even though an element is opted into both color schemes, the first is what gets first preference. Setting both values on the :root is a good way to make sure all of your elements support color schemes, while leaning into one over the other.

The exact colors are up to the browser

Browsers include their own stylesheets. We call these User Agent (which is just a fancy word for “browser”) styles. Let’s peek at one of the CSS in WebKit’s stylesheet:

a:any-link {
  color: -webkit-link;
  text-decoration: underline;
  cursor: auto;
}

See that -webkit-link color? That’s a named color only WebKit recognizes and it applies that color to all links by default, unless we override it with our own CSS. So, when a rendering engine like WebKit sees that we’ve set color-scheme: light or color-scheme: dark on an element, it decides which version of that color will work best for the situation.

The spec elaborates on this:

Light and dark color schemes are not specific color palettes. For example, a stark black-on-white scheme and a sepia dark-on-tan scheme would both be considered light color schemes. To ensure particular foreground or background colors, they need to be specified explicitly.

But sometimes the color is up to the user

Operating systems often allow users to override its system colors with a preferred color scheme.

macOS system preferences open to appearance settings.
macOS allows users to change the system’s “Accent color” and “Highlight color” in addition to selecting a “Light”, “Dark”, or “Auto” color scheme.

These are the preferences a browser checks when it sees color-scheme declared on an element. If the user selects a color from these preferences, then the browser’s default styles will respect those operating system settings.

There’s a corresponding <meta> tag, too

We can get the same benefits by adding the <meta name="color-scheme"> tag to the <head> in the HTML of a webpage:

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

There’s no need to specify the color scheme in both the HTML and CSS, but it could be handy in situations where your stylesheet fails to load for some reason. That way, the page is still opted into both color schemes, even if the CSS is unavailable.

If you’re trying to choose between the HTML meta tag and the CSS property, you might lean toward the HTML. HTML is instantly available. The CSS method, on the other hand, requires the stylesheet to download before it applies the property and the time it takes to do that could result in a slight flash of incorrect color theme.

color-scheme is all about default appearances

So, elements on a page that have default colors from the User Agent stylesheet — like the background-color of form controls and the color of text — update according to the color-scheme value. Apple explains this nicely it’s “Supporting Dark Mode in Your Web Content” video:

[color-scheme] changes the default text and background colors of the page to match the current system appearance, standard form controls, scrollbars and other named system colors also change their look automatically.

We can see this with a quick and dirty demo where we’re only dealing with three elements:

  • The document :root,
  • a <h1>element, and
  • a <button>

Notice that the demo does not set any color on the elements. I’ve set the :root to a light color scheme so that is what renders by default. Clicking the button set :root to dark.

See what changed when going from light to dark?

  • The document’s background-color changes from white to black.
  • The Heading 1 color changes from black to white.
  • The button’s color also changes from black to white.
  • The button’s background-color changes shades

If we crack open DevTools, we can see that the Heading 1 color is coming from the User Agent stylesheet with a text value.

DevTools window with the Heading 1 element selected.

I’m honestly unsure what color text actually maps to. But if we move over to the Computed tab, we’ll see that it’s black (rgb(0, 0, 0)).

DevTools open showing the computed Heading 1 styles.
The text value is mapped to a color of rgb(0, 0, 0) when color-scheme is set to light.

When we toggle the color-scheme property from light to dark, that computed value changes to white (rgb(255, 255, 255)):

DevTools open showing the computed Heading 1 styles.
The text value is mapped to a color of rgb(255, 255, 255) when color-scheme is set to dark.

Those colors were never set in the styles I wrote — they come from the browser’s stylesheet. And the color-scheme property is what controls them.

color-scheme is different than prefers-color-scheme

It’s easy to confuse the two. They take the same light and dark values afterall!

The difference? Again, color-scheme is all about default appearances. It tells the browser to update the colors in its stylesheet.

Meanwhile, prefers-color-scheme is all about applying the styles we write in our own stylesheet, and only when that condition is met. In other words, any style rules we write inside the media query are applied — it has nothing to do with the browser’s default styles.

Let’s revisit our last example where we changed the color-scheme of a page that contains the :root document, a heading, and a button. By default, I did not change any of the colors, but I did declare color-scheme: light. That means all of the colors are coming from the browser’s stylesheet.

:root {
  color-scheme: light;
}

When we changed that to:

:root {
  color-scheme: dark;
}

…all of those default colors from the browser’s stylesheet changed. No media queries required!

Now let’s say we never declare color-scheme. We’d use the prefers-color-scheme media query to change those colors ourselves. Here’s an abbreviated example that changes the :root element’s background-color and color:

:root {
  background-color: white;
  color: black;
}

/* If user prefers a dark appearance */
@media (prefers-color-scheme: dark) {
  :root {
    background-color: black;
    color: white;
  }
}

The media query is only applied if the user’s operating system appearance is set to “Dark”.

macOS system preferences open to appearance settings with Dark mode selected.

If that condition matches, any styles we put in there are applied — we’re not limited only to the browser’s default colors and styles. For example, let’s change background-color and color to completely different colors when the prefers-color-scheme: dark:

Using color-scheme and prefers-color-scheme together

The truth is that it’s better to use the two together. Why? They do different things but complement each other nicely. We could create the most beautiful dark mode interface in the world using prefers-color-scheme, but some elements — like form controls and scrollbars — are outside our control without overriding a bunch of default browser styles. When we use the two together, we gain an easy way toi switch those defaults without the additional overhead.

Browser support

Support for the CSS color-scheme property is pretty solid, as long as you’re using the light and dark values. Many browsers lack support for the only keyword at the time of writing, with Safari as the lone exception.


Desktop Browsers
IEEdgeChromeFirefoxSafariOpera
color-schemeNo81+81+96+13+68+
Keyword: only darkNoNoNoNo13+No
Keyword: only lightNoNoNoNo13+No
Mobile BrowsersSafari iOSChrome AndroidFirefox AndroidAndroid BrowserOpera Mobile
color-scheme13+108+107+108+72+
Keyword: only light13+NoNoNoNo
Keyword: only dark13+NoNoNoNo
Source: Caniuse

More information