Cached at:
07/05/26, 10:29 AM
# Dark mode with web standards
Source: [https://olliewilliams.xyz/blog/dark-mode/](https://olliewilliams.xyz/blog/dark-mode/)
Respecting the user’s OS setting is straightforward: use the`prefers\-color\-scheme`media query in CSS\. Arguably, that isn’t enough: users should also be able to customise their choice on a per\-site basis\. A user might want dark mode for the UI of an application, but light mode for reading long\-form text on content\-heavy sites, for example\.
We need to:
1. Support the user’s system setting as the default for when the user lands on our website for the first time
2. Allow the user to override their system setting with a toggle in our application\.
The color scheme of a web page can be set either via a HTML meta tag in the`<head\>`of the document or via the CSS`color\-scheme`property on the`html`element\. It can take time for CSS to load on slow connections, so using the meta tag is the recommended approach\. When a user lands on your website for the first time, respect their system preference by setting`<meta name="color\-scheme" content="light dark"\>`\. To override the OS setting via a control in your web app or site, use JavaScript to update the`content`attribute value to`light`to force light mode,`dark`to force dark mode, or`light dark`to revert back to the OS setting\.
```
const metaTag = document.querySelector('[name="color-scheme"]');
const savedScheme = localStorage.getItem("colorScheme");
if (savedScheme) {metaTag.setAttribute('content', savedScheme);}
btnlight.addEventListener('click', function() {
metaTag.setAttribute('content', 'light');
localStorage.setItem("colorScheme", "light");
});
btndark.addEventListener('click', function() {
metaTag.setAttribute('content', 'dark');
localStorage.setItem("colorScheme", "dark");
});
btnsystem.addEventListener('click', function() {
metaTag.setAttribute('content', 'light dark');
localStorage.removeItem("colorScheme");
});
```
#### What does the`color\-scheme`affect?
- Colors, gradients, or images set via the`light\-dark\(\)`CSS function
- System colors like`Canvas`and`CanvasText`
- Scrollbar colors
- The default colors of HTML elements like buttons
- iframes styles \(so long as the iframe document has opted in via the meta tag\)
- SVG’s that make use of`light\-dark\(\)`or`prefers\-color\-scheme`
#### What doesn’t`color\-scheme`affect?
There’s an unfortunate disconnect between`color\-scheme`and the`prefers\-color\-scheme`media query\.`prefers\-color\-scheme`reflects the OS settings — regardless of the`color\-scheme`value\. If you’re providing an in\-page toggle that implements dark mode, you can’t adopt the`prefers\-color\-scheme`media query\.
The following code is not be impacted by`color\-scheme`:
```
<picture>
<source srcset="logo-dark.png" media="(prefers-color-scheme: dark)" />
<img src="logo-light.png" alt="Product logo" />
</picture>
```
Other than making use of a`background\-image`, there’s sadly not an equivalent approach to the`<picture\>`element that references the`color\-scheme`\.
There are two exceptions where`color\-scheme`will affect the`prefers\-color\-scheme`media query:
- iframes
- SVG
Below are two iframes that show the same document\. The document inside the iframe is making use of the`prefers\-color\-scheme`media query\.
```
<iframe style="color-scheme: light;" src="/example.html"></iframe>
<iframe style="color-scheme: dark;" src="/example.html"></iframe>
```
As you can see in the above example, the`@media \(prefers\-color\-scheme: dark\)`styles are being applied when`color\-scheme: dark`is used in the parent document\.
The same principle applies to SVG\. Here’s the content of an`\.svg`file:
```
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<style>
circle {
fill: rgb(40,40,40);
}
@media (prefers-color-scheme: dark) {
circle {
fill: rgb(216, 216, 216);
}
}
</style>
<circle cx="50" cy="50" r="50" />
</svg>
```
```
<img style="color-scheme: light;" src="/circle.svg" alt="">
<img style="color-scheme: dark;" src="/circle.svg" alt="">
```

The CSS spec has recently been[updated](https://github.com/w3c/csswg-drafts/pull/13857#:~:text=This%20PR%20applies%20the%20resolution%20from%20the%20recent%20F2F%20in%20#13377%2C%20making%20(prefers%2Dcolor%2Dscheme)%20reflect%20the%20effects%20of%20%3Cmeta%20name=color%2Dscheme%3E)so that the color\-scheme of the document does effect the media query in all contexts, but no browser has implemented this\.
#### Some Safari caveats
- Support for the`prefers\-color\-scheme`media query within SVG was added in[Safari 27](https://developer.apple.com/documentation/safari-release-notes/safari-27-release-notes?changes=la,la#:~:text=175598175)-,Fixed%20an%20issue%20where,the%20system%20color%20appearance,-.%20(176413340), but`color\-scheme`does not affect the media query \([see bug report](https://bugs.webkit.org/show_bug.cgi?id=316640)\)
- Support for the`prefers\-color\-scheme`media query within iframes was added in[Safari 27](https://developer.apple.com/documentation/safari-release-notes/safari-27-release-notes?changes=la,la#:~:text=140674753)-,Fixed%20an%20issue%20where,was%20set%20to%20dark,-.%20(142072593)and the`color\-scheme`of the parent document does override it, as it should 🎉\. However, other bugs remain \([see bug report](https://bugs.webkit.org/show_bug.cgi?id=316680)\)
## Using`light\-dark\(\)`with images and gradients
The`light\-dark\(\)`function was originally limited to colors\. It can now be used for gradients and images \(as of Chrome/Edge version 150, Firefox version 150, and Safari Technology Preview\)\.
```
.bg-gradient {
background-image: light-dark(linear-gradient(15deg, #b9b6ff, #308dc6), linear-gradient(15deg, #6b7495, #001339));
}
```
```
<div class="bg-gradient" style="color-scheme: light;"></div>
<div class="bg-gradient" style="color-scheme: dark;"></div>
```
It’s also possible to switch between a single solid color and a gradient, depending on the`color\-scheme`\.
```
.bg-grad-solid {
background-image: light-dark(linear-gradient(15deg, #b9b6ff, #308dc6), image(#001339));
}
```
### Images
```
.bg {
background-image: light-dark(url(/lightmode.avif), url(/darkmode.avif));
}
```
## Changing more than colors, images and gradients
By and large, the thing you need to change between modes is color, but there are exceptions\. A`box\-shadow`might not be visible on a dark background in dark mode, so you may decide to apply a border instead, for example\. Implementing that is currently rather challenging\. The CSS standards body is planning to add a way to[detect the current color\-scheme](https://github.com/w3c/csswg-drafts/issues/10577#issuecomment-3329616811)using either a CSS`if\(\)`statement or a style query, but no browser has implemented this feature\. It’s possible to hack together an alternative\. Below I’ll look at two approaches\.
Define a CSS variable to be true when the page is using dark mode:
```
html {
--dark: false;
}
html:has([content="light dark"]) {
@media (prefers-color-scheme: dark) {
--dark: true;
}
}
html:has([content="dark"]) {
--dark: true;
}
```
All browsers now support style queries, which allow you to apply styles depending on the value of a custom property:
```
@container style(--dark: false) {
.card {
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
}
}
@container style(--dark: true) {
.card {
border: solid 1px rgb(94, 94, 94);
}
}
```
A better approach that also uses style queries would be:
```
@property --usedScheme {
syntax: "<color>";
inherits: true;
initial-value: transparent;
}
body {
--usedScheme: light-dark(white, black);
}
@container style(--usedScheme: white) {
.card {
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
}
}
@container style(--usedScheme: black) {
.card {
border: solid 1px rgb(94, 94, 94);
}
}
```
## A \(possible\) future: overriding`prefers\-color\-scheme`via JS
We might get a way to override the`prefers\-color\-scheme`media query with JavaScript in the future\. There’s a[spec](https://drafts.csswg.org/mediaqueries-5/#script-control-user-prefs), an[MDN entry](https://developer.mozilla.org/en-US/docs/Web/API/User_Preferences_API)and a prototype in Chrome Canary but the Safari team are[opposed to the idea](https://github.com/WebKit/standards-positions/issues/252)\.