UGURUS offers elite coaching and mentorship for agency owners looking to grow. Start with the free Agency Accelerator today.
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.
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.
<meta>
tag, too
There’s a corresponding 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.
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)
).
When we toggle the color-scheme
property from light
to dark
, that computed value changes to white (rgb(255, 255, 255)
):
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”.
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
:
color-scheme
and prefers-color-scheme
together
Using 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 | IE | Edge | Chrome | Firefox | Safari | Opera |
---|---|---|---|---|---|---|
color-scheme | No | 81+ | 81+ | 96+ | 13+ | 68+ |
Keyword: only dark | No | No | No | No | 13+ | No |
Keyword: only light | No | No | No | No | 13+ | No |
Mobile Browsers | Safari iOS | Chrome Android | Firefox Android | Android Browser | Opera Mobile |
---|---|---|---|---|---|
color-scheme | 13+ | 108+ | 107+ | 108+ | 72+ |
Keyword: only light | 13+ | No | No | No | No |
Keyword: only dark | 13+ | No | No | No | No |
More information
- CSS Color Adjustment Level Module 1 (W3C)
- A Quick Look at the First Public Working Draft for Color Adjust Module 1 (CSS-Tricks)
- Don’t Forget the color-scheme Property (Jim Nielsen)
- Improved dark mode default styling with the
color-scheme
CSS property (web.dev)
Related tricks!
Personalize it!
A Complete Guide to Dark Mode on the Web
Honor prefers-color-scheme in the CSS Paint API with Custom Properties
Dark Mode in CSS
Flash of inAccurate coloR Theme (FART)
Related
background
.element { background: url(texture.svg) top center / 200px 200px no-repeat fixed #f8a100; }
border
.element { border: 3px solid #f8a100; }
caret-color
.element { caret-color: red; }
color
.element { color: #f8a100; }
outline-color
.element { outline-color: #f8a100; }
text-decoration-color
.element { text-decoration-color: orange; }
text-emphasis
.element { text-emphasis: circle red; }
text-shadow
p { text-shadow: 1px 1px 1px #000; }
column-rule-color
.element { column-rule-color: #f8a100; }