CSS Module 6 – Colour and theming
Dark mode, colour schemes and forced colours
Support light and dark themes with color-scheme, prefers-color-scheme and light-dark(), add a theme switch, and keep pages usable in forced colours.
What you will learn
- Support light and dark themes with color-scheme, prefers-color-scheme and light-dark()
- Add a user theme switch that overrides the system preference without duplicating rules
- Check every colour pair of a theme in both schemes
- Design pages that stay usable in forced-colours (high-contrast) mode
Before you start
What you’ll learn · Before you start
What you will learn
- Support light and dark themes with color-scheme, prefers-color-scheme and light-dark()
- Add a user theme switch that overrides the system preference without duplicating rules
- Check every colour pair of a theme in both schemes
- Design pages that stay usable in forced-colours (high-contrast) mode
Before you start
On this page
Dark mode takes three pieces. The user’s setting reaches the page as the prefers-color-scheme media feature,
light or dark. The page says which schemes it supports with the color-scheme property, usually
color-scheme: light dark on the root element, or with a <meta name="color-scheme"> element; that one declaration
lets the browser draw form controls, scrollbars, the page background and its system colours in the dark scheme. Your
own colours then switch with light-dark(), which takes a light and a dark value, or with rules inside
@media (prefers-color-scheme: dark). A theme menu that overrides the system needs only one change: set
color-scheme on the root to light, dark or light dark. Forced colours mode, which the contrast themes of
Windows turn on, goes further: it replaces the page’s colours with the user’s own palette, so a page must stay usable
without its colours.
This lesson measures each piece in Chromium 153, Firefox 155 and WebKit 26.6 with light, dark and forced colours emulated, and reproduces the two mistakes that break most dark themes.
color-scheme: what the page supports
color-scheme takes normal, the default, or a list of the schemes an element is designed for: light, dark, or
both. When the user’s preferred scheme is in the list, the element uses it; otherwise it uses the first scheme
listed; with normal and nothing else on the page, it supports no scheme and gets the browser’s default, which is
light. The keyword only, as in only light, also forbids the browser from forcing its own automatic dark version on
the element. The property is inherited, so :root { color-scheme: light dark; } covers the whole page, and a part of
the page can still opt out with its own value.
The used scheme changes what the browser draws for you: the default colours of form controls, scrollbars and other
built-in parts, and, on the root, the page’s canvas behind everything. It also changes the system colours. This page
reads them in two panels, one with color-scheme: light and one with color-scheme: dark:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>System colours in the light and the dark scheme</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 16px; }
.light { color-scheme: light; }
.dark { color-scheme: dark; }
.panel { padding: 12px; margin-block-end: 12px; background-color: Canvas; color: CanvasText; }
</style>
</head>
<body>
<div class="panel light"><input value="A field in the light scheme"> <a href="#top">a link</a></div>
<div class="panel dark"><input value="A field in the dark scheme"> <a href="#top">a link</a></div>
<script>
const keywords = ['Canvas', 'CanvasText', 'LinkText', 'Field', 'FieldText', 'ButtonFace', 'ButtonText', 'GrayText', 'Highlight'];
for (const panel of document.querySelectorAll('.panel')) {
const probe = document.createElement('span');
panel.append(probe);
const values = keywords.map((k) => {
probe.style.color = k;
return `${k} ${getComputedStyle(probe).color}`;
});
probe.remove();
const field = getComputedStyle(panel.querySelector('input'));
console.log(`${panel.className.replace('panel ', '')}: ${values.join(', ')}`);
console.log(` the input: background ${field.backgroundColor}, text ${field.color}`);
}
</script>
</body>
</html> Under color-scheme: dark, the three engines reported:
Canvas:rgb(18, 18, 18)in Chromium 153,rgb(28, 27, 34)in Firefox 155,rgb(30, 30, 30)in WebKit 26.6.CanvasText:rgb(255, 255, 255),rgb(251, 251, 254)andrgb(255, 255, 255).LinkText:rgb(158, 158, 255),rgb(0, 202, 219)andrgb(158, 158, 255).Field:rgb(59, 59, 59),rgb(43, 42, 51)and the translucentrgba(255, 255, 255, 0.247).
In the light panel all three engines agreed on a white Canvas, black CanvasText and the classic link blue
rgb(0, 0, 238). In the dark panel each engine used its own palette, and the input turned dark in all three. That
is the point of system colours: use them where something should look like the browser’s own interface, and set
your own colours wherever a design needs exact values.
A stylesheet loads after the HTML has started to render, so a page that sets color-scheme only in CSS can flash
white for a moment in dark mode. The HTML Standard’s answer is the meta element, which the browser reads before any
CSS: <meta name="color-scheme" content="light dark">. Use it together with the CSS property. Both are Baseline
widely available.
light-dark() follows the scheme, not the media query
light-dark(#ffffff, #1c1f26) computes to its first colour where the element’s used scheme is light and to its
second where it is dark. It does not read prefers-color-scheme itself, which is where most attempts to use it go
wrong:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>light-dark() with and without color-scheme</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 16px; }
.card {
--surface: light-dark(#ffffff, #1c1f26);
--text: light-dark(#1a1a1a, #e8eaed);
--muted: light-dark(#5f6368, #a8adb5);
--border: light-dark(#8a8f98, #6b717c);
--accent: light-dark(#0044cc, #8ab4ff);
padding: 12px;
margin-block-end: 12px;
color: var(--text);
background-color: var(--surface);
border: 1px solid var(--border);
}
.card small { color: var(--muted); }
.card a { color: var(--accent); }
/* light-dark() picks the dark value only where the used colour scheme is dark. */
.supports-both { color-scheme: light dark; }
</style>
</head>
<body>
<div class="card" id="default">Card A, no color-scheme <small>last sync 2 min ago</small> <a href="#top">Details</a></div>
<div class="supports-both">
<div class="card" id="both">Card B, inside color-scheme: light dark <small>last sync 2 min ago</small> <a href="#top">Details</a></div>
</div>
<script>
console.log(`prefers dark: ${matchMedia('(prefers-color-scheme: dark)').matches}`);
for (const card of document.querySelectorAll('.card')) {
const s = getComputedStyle(card);
const muted = getComputedStyle(card.querySelector('small')).color;
const accent = getComputedStyle(card.querySelector('a')).color;
console.log(`${card.id.padEnd(7)} surface ${s.backgroundColor}, text ${s.color}, muted ${muted}, accent ${accent}`);
}
</script>
</body>
</html> With the system set to dark, card A, which sits on a page with no color-scheme, stayed white with dark text in all
three engines: its used scheme was the light default. Card B, inside an element with color-scheme: light dark,
switched to the surface rgb(28, 31, 38) and the text rgb(232, 234, 237). With the system set to light, both cards
were light. One declaration on the root is the whole fix.
light-dark() for colours is Baseline newly available. A newer form also accepts two images, such as two gradients,
and is newly available in the latest browsers only; where older browsers matter, put a plain declaration before the
one with light-dark(), and they keep it.
A theme needs every colour pair checked twice, once per scheme, because a colour that works on one surface can fail on the other. The program below checks the card’s tokens:
// Every colour pair of the dashboard card in light-dark-card.html, checked in both schemes against WCAG 2.2:
// 4.5:1 for text and 3:1 for the border, which shows where the card ends (1.4.11). The last row reuses the light
// theme's accent in the dark theme, a common mistake when a dark theme is added later.
const tokens = {
surface: ['#ffffff', '#1c1f26'],
text: ['#1a1a1a', '#e8eaed'],
muted: ['#5f6368', '#a8adb5'],
border: ['#8a8f98', '#6b717c'],
accent: ['#0044cc', '#8ab4ff'],
};
const luminance = (hex) => {
const lin = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255).map((c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
};
const contrast = (a, b) => {
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
};
const checks = [
['text on surface', 'text', 4.5],
['muted on surface', 'muted', 4.5],
['accent on surface', 'accent', 4.5],
['border on surface', 'border', 3],
];
console.log('pair light dark target');
for (const [label, token, target] of checks) {
const [light, dark] = [0, 1].map((i) => contrast(tokens[token][i], tokens.surface[i]));
const verdict = light >= target && dark >= target ? 'pass' : 'FAIL';
console.log(`${label.padEnd(20)} ${light.toFixed(2).padStart(5)} ${dark.toFixed(2).padStart(5)} ${String(target).padStart(6)} ${verdict}`);
}
const reused = contrast(tokens.accent[0], tokens.surface[1]);
console.log(`light accent on the dark surface: ${reused.toFixed(2)} ${reused >= 4.5 ? 'pass' : 'FAIL'}`); Output
pair light dark target text on surface 17.40 13.68 4.5 pass muted on surface 6.05 7.31 4.5 pass accent on surface 7.78 7.89 4.5 pass border on surface 3.25 3.36 3 pass light accent on the dark surface: 2.12 FAIL
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node token_contrast.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Every pair passes in both schemes, the border at 3.25:1 and 3.36:1 against the 3:1 that marks where the card ends. The last line is the mistake that turns up when a dark theme is added to an existing site: the light theme’s link blue on the dark surface reaches only 2.12:1. Dark themes need their own, lighter accent colours.
Three inputs decide the used colour scheme, and four things follow it
Text description of the diagram
The diagram shows three boxes feeding one result.
- The user's setting, which pages read as the prefers-color-scheme media feature: light or dark.
- What the page says it supports, with the color-scheme property or the meta element of the same name. Without either, an element supports no scheme and gets the browser's default, light.
- Forced colours mode. When it is on, color-scheme computes to light dark and the user's palette replaces the page's colours.
Together they give the element's used colour scheme, light or dark: the preferred scheme if the element supports it, otherwise the first scheme it lists. Four things follow the used scheme: which value light-dark() picks, the values of system colours such as Canvas and Field, the colours of form controls and scrollbars, and the background of the page canvas.
A theme switch without duplicated rules
Some readers want a different theme from their system’s. Because light-dark(), form controls and system colours all
follow the used scheme, a theme menu only has to change color-scheme on the root element: light or dark to
choose one, light dark to follow the system again. Nothing else in the stylesheet changes:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<!-- Says before any CSS loads that the page supports both schemes, so the first paint can already be dark. -->
<meta name="color-scheme" content="light dark">
<title>A theme menu that only changes color-scheme</title>
<style>
:root { color-scheme: light dark; }
body { font: 16px/1.5 system-ui, sans-serif; margin: 16px; }
.card {
padding: 12px;
margin-block: 12px;
color: light-dark(#1a1a1a, #e8eaed);
background-color: light-dark(#ffffff, #1c1f26);
}
/* A rule written with the media query follows the operating system, never the menu. */
.banner { padding: 8px; background-color: #e8f0fe; }
@media (prefers-color-scheme: dark) {
.banner { background-color: #263238; }
}
</style>
</head>
<body>
<label for="theme">Theme</label>
<select id="theme">
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<div class="card">Dashboard card</div>
<div class="banner">Banner styled with a media query</div>
<script>
// One change for the whole page: color-scheme on the root element. "System" lets the browser follow the
// operating system again. A real site would also save the choice; this demo keeps it in memory only.
const schemes = { system: 'light dark', light: 'light', dark: 'dark' };
const menu = document.getElementById('theme');
const report = () => {
const card = getComputedStyle(document.querySelector('.card'));
const banner = getComputedStyle(document.querySelector('.banner'));
const field = getComputedStyle(menu);
console.log(`${menu.value.padEnd(6)} card ${card.backgroundColor} on text ${card.color}; menu ${field.backgroundColor}; banner ${banner.backgroundColor}`);
};
menu.addEventListener('change', () => {
document.documentElement.style.colorScheme = schemes[menu.value];
requestAnimationFrame(report);
});
console.log(`prefers dark: ${matchMedia('(prefers-color-scheme: dark)').matches}`);
report();
</script>
</body>
</html> The page was loaded with the system set to light and then to dark, and each menu choice was made in turn. All three engines behaved the same way:
- System light, menu on System or Light: the card was white and the menu field light; the banner from the
media query was light,
rgb(232, 240, 254). - System light, menu on Dark: the card turned
rgb(28, 31, 38)and the menu field dark; the banner stayed light. - System dark, menu on System or Dark: the card was
rgb(28, 31, 38)and the menu field dark; the banner was dark,rgb(38, 50, 56). - System dark, menu on Light: the card was white and the menu field light; the banner stayed dark.
The card and the native select followed the menu. The banner, written with @media (prefers-color-scheme: dark),
followed only the system, because that media feature reports the user’s setting and knows nothing about the page’s
menu. A site that offers a theme switch should write its colours with light-dark(); with media queries it would have
to repeat every dark rule under a class or an attribute set by the menu, which is the duplication this approach
avoids.
A real site also remembers the choice, usually in localStorage, and applies it before the first paint, with a small
script in the page’s head or with the value written into the HTML by the server, so returning readers never see the
wrong theme flash. The HTML, CSS & JS Playground runs your code in an isolated frame where localStorage is not
available, so the demo keeps the choice in memory only. Without any script, radio buttons and :has() can do the
same: :root:has(#theme-dark:checked) { color-scheme: dark; }, but nothing will remember the choice.
Forced colours mode
Forced colours mode is an accessibility setting: the user picks a palette, often of high contrast, and the browser
applies it to every page. Pages can detect it with @media (forced-colors: active), Baseline widely available, and
the palette is available to them through the system colours. CSS Color Adjustment Level 1 lists what changes. The
colour properties, such as color, background-color, border-color, outline-color, fill and stroke, are given
system colours, box-shadow and text-shadow compute to none, background-image computes to none unless it holds
a url() image, color-scheme computes to light dark, and accent-color and scrollbar-color go back to auto.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A status list and a button in forced colours mode</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 16px; }
.status { display: inline-block; padding: 2px 10px; border-radius: 999px; color: white; }
.status.ok { background-color: #1e7e34; }
.status.down { background-color: #c62828; }
/* Without this border, the two chips differ only in background colour. */
.status { border: 1px solid transparent; }
.status .icon { margin-inline-end: 4px; }
.action {
padding: 8px 16px;
font: inherit;
color: white;
background-color: #0044cc;
border: 2px solid transparent;
border-radius: 6px;
}
.action:focus-visible {
outline: 2px solid transparent; /* becomes a visible system colour when colours are forced */
box-shadow: 0 0 0 4px #f26b1d; /* removed when colours are forced */
}
@media (forced-colors: active) {
.status.down { border-style: dashed; } /* a second cue besides colour */
.action:focus-visible { outline-offset: 2px; }
}
</style>
</head>
<body>
<span class="status ok"><span class="icon" aria-hidden="true">✓</span>Online</span>
<span class="status down"><span class="icon" aria-hidden="true">✕</span>Offline</span>
<button class="action" type="button">Restart</button>
<script>
const forced = matchMedia('(forced-colors: active)').matches;
console.log(`forced colours: ${forced}, root color-scheme ${getComputedStyle(document.documentElement).colorScheme}`);
for (const chip of document.querySelectorAll('.status')) {
const s = getComputedStyle(chip);
console.log(`${chip.textContent.padEnd(8)} background ${s.backgroundColor}, text ${s.color}, border ${s.borderTopColor} ${s.borderTopStyle}`);
}
const button = document.querySelector('.action');
button.focus({ focusVisible: true });
const b = getComputedStyle(button);
console.log(`focused button: outline ${b.outlineColor} ${b.outlineStyle} ${b.outlineWidth}, box-shadow ${b.boxShadow}`);
</script>
</body>
</html> The page was loaded with Playwright’s forced colours emulation. In Chromium and Firefox:
- Both status chips got a white background and black text: the green and the red were gone. What still told
them apart were the words, the icons, and the dashed border that the
forced-colorsrule gives the offline chip. Colour must never be the only way to show a state, which is also what WCAG 2.2’s success criterion 1.4.1 asks. - The transparent borders became visible system colours,
rgb(0, 0, 0)on the light palette, so the chips kept their outlines. - The focus ring drawn with
box-shadowcomputed tonone, but the transparent outline beside it got a visible colour,rgba(5, 0, 73, 0.8)in Chromium andrgb(0, 0, 0)in Firefox, so keyboard users could still see focus. color-schemeon the root computed tolight darkin Chromium, as the specification says; Firefox still reportednormal.
WebKit matched the emulated forced-colors: active query, so the dashed border appeared, but it replaced no colour at
all. Test this mode in Chromium or Firefox, in DevTools’ rendering settings or with Playwright, and on Windows with a
contrast theme before a release.
Write rules for this mode sparingly, and only with system colours: Highlight for a selected item or ButtonText for
an icon, for example. forced-color-adjust: none takes an element out of the mode entirely and is meant for content
whose colour is the information, such as a colour swatch; it has limited availability, because Safari doesn’t support
it. In forced colours mode browsers also match prefers-contrast: more or less when the user’s palette is clearly
of high or low contrast (custom otherwise), and the matching prefers-color-scheme value when it is clearly dark or
light.
Practise: the colour scheme a browser uses
Write the rules that decide the used colour scheme, the value a theme menu sets, and what forced colours mode removes:
Exercise · Medium · JavaScript
Work out the colour scheme a browser uses, and what forced colours remove
light-dark(), form controls and system colours all follow the used colour scheme of an element, and a theme switch works only if you know how the browser picks it. Write three functions in theme.mjs and export them, following CSS Color Adjustment Level 1.
usedScheme(value, preferred, pageSchemes = null) returns 'light' or 'dark'. value is the element's computed color-scheme, such as 'normal', 'dark', 'light dark' or 'only light'; preferred is the user's prefers-color-scheme, 'light' or 'dark'; pageSchemes is the content of the page's <meta name="color-scheme">, or null when there is none.
- normal means the page's supported schemes from the meta element; without one, the element supports no scheme. - Only light and dark count. only and any other word are ignored. - If the preferred scheme is in the list, use it; otherwise use the first scheme in the list; with an empty list, use the browser's default, 'light'.
themeValue(choice) turns the choice of a theme menu into the value to set on the root element's color-scheme: 'system' gives 'light dark', 'light' gives 'light' and 'dark' gives 'dark'. Anything else throws a RangeError.
forcedComputed(property, value) returns a property's computed value once forced colours mode is on, for the properties the specification changes: box-shadow and text-shadow become 'none', background-image becomes 'none' unless its value contains a url( image, color-scheme becomes 'light dark', and accent-color and scrollbar-color become 'auto'. Every other property keeps its value; colours are replaced later, when the browser paints.
The sample tests import the three functions from theme.mjs and run in your browser.
Starter code · theme.mjs
/** The used colour scheme, 'light' or 'dark', from color-scheme, the user's preference and the page's meta element. */
export function usedScheme(value, preferred, pageSchemes = null) {
// Replace this line with your code.
return 'light';
}
/** The color-scheme value a theme menu sets on the root element for 'system', 'light' or 'dark'. */
export function themeValue(choice) {
// Replace this line with your code.
return 'normal';
}
/** A property's computed value in forced colours mode (CSS Color Adjustment 1, section 3.1). */
export function forcedComputed(property, value) {
// Replace this line with your code.
return value;
} The sample tests · theme.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { usedScheme, themeValue, forcedComputed } from './theme.mjs';
test('the preferred scheme wins when the element supports it', () => {
assert.equal(usedScheme('light dark', 'dark'), 'dark');
assert.equal(usedScheme('light dark', 'light'), 'light');
assert.equal(usedScheme('dark light', 'light'), 'light');
});
test('otherwise the first scheme the element lists', () => {
assert.equal(usedScheme('dark', 'light'), 'dark');
assert.equal(usedScheme('light', 'dark'), 'light');
assert.equal(usedScheme('only light', 'dark'), 'light');
});
test('normal follows the meta element, or the browser default without one', () => {
assert.equal(usedScheme('normal', 'dark'), 'light');
assert.equal(usedScheme('normal', 'dark', 'light dark'), 'dark');
assert.equal(usedScheme('normal', 'light', 'dark'), 'dark');
});
test('words other than light and dark are ignored', () => {
assert.equal(usedScheme('light sepia', 'dark'), 'light');
assert.equal(usedScheme('sepia', 'dark'), 'light');
assert.equal(usedScheme('sepia dark', 'light'), 'dark');
});
test('a theme menu sets one value on the root element', () => {
assert.equal(themeValue('system'), 'light dark');
assert.equal(themeValue('light'), 'light');
assert.equal(themeValue('dark'), 'dark');
assert.throws(() => themeValue('sepia'), RangeError);
});
test('the menu and usedScheme together: dark wins over a light system only when chosen', () => {
assert.equal(usedScheme(themeValue('dark'), 'light'), 'dark');
assert.equal(usedScheme(themeValue('light'), 'dark'), 'light');
assert.equal(usedScheme(themeValue('system'), 'dark'), 'dark');
});
test('forced colours remove shadows and gradients', () => {
assert.equal(forcedComputed('box-shadow', '0 0 0 4px #f26b1d'), 'none');
assert.equal(forcedComputed('text-shadow', '1px 1px 0 red'), 'none');
assert.equal(forcedComputed('background-image', 'linear-gradient(red, blue)'), 'none');
assert.equal(forcedComputed('background-image', 'url("photo.jpg"), linear-gradient(red, blue)'), 'url("photo.jpg"), linear-gradient(red, blue)');
});
test('forced colours reset color-scheme, accent-color and scrollbar-color, and keep the rest', () => {
assert.equal(forcedComputed('color-scheme', 'dark'), 'light dark');
assert.equal(forcedComputed('accent-color', '#0044cc'), 'auto');
assert.equal(forcedComputed('scrollbar-color', 'red blue'), 'auto');
assert.equal(forcedComputed('outline-color', 'transparent'), 'transparent');
assert.equal(forcedComputed('border-radius', '6px'), '6px');
}); A hint
For usedScheme, first choose the text to read: pageSchemes when value is 'normal' (and an empty string when there is no meta element), otherwise value. Split it on spaces and keep only the words light and dark; the rest is three ifs. forcedComputed is a switch on the property, with /url\(/i.test(value) for background-image.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · What does color-scheme: light dark on the root element do?
It tells the browser that the page is designed for both a light and a dark colour scheme, so the browser may use the
dark one when the user prefers it. The browser then draws everything it controls in that scheme: the page background,
form controls, scrollbars and the system colours such as Canvas and Field. It also decides which value
light-dark() picks, so light-dark(white, black) turns black only where the used scheme is dark. It does not change
any colour the page sets itself with a plain value. Without it, the used scheme stays light even when the user prefers
dark, which is why light-dark() seems not to work. A <meta name="color-scheme" content="light dark"> element says
the same before the CSS loads, which avoids a white flash.
Key takeaways
- Put
color-scheme: light darkon:rootand the matching meta element in the HTML: form controls, scrollbars, the canvas and system colours then follow the reader’s scheme. light-dark()follows the used colour scheme, notprefers-color-scheme: withoutcolor-schemeit always picks its light value.- A theme menu needs one change,
color-schemeon the root; rules written insideprefers-color-schememedia queries ignore it. - Check every colour pair in both schemes; a dark theme needs its own lighter accents.
- In forced colours mode shadows and gradients vanish and colours are replaced: carry meaning in text, icons and borders, and draw focus with an outline.
References
- CSS Color Adjustment Module Level 1: the color-scheme property (W3C CSS Working Group)
- CSS Color Adjustment Module Level 1: effects of the used colour scheme (W3C CSS Working Group)
- CSS Color Adjustment Module Level 1: properties affected by forced colors mode (W3C CSS Working Group)
- CSS Color Adjustment Module Level 1: the forced-color-adjust property (W3C CSS Working Group)
- CSS Color Module Level 5: the light-dark() function (W3C CSS Working Group)
- Media Queries Level 5: prefers-color-scheme and forced-colors (W3C CSS Working Group)
- HTML Standard: the color-scheme meta name (WHATWG)
- CSS Color Module Level 4: system colours (W3C CSS Working Group)
- Web Content Accessibility Guidelines (WCAG) 2.2: Use of Color (W3C)
- light-dark() (MDN Web Docs) (Mozilla)
- color-scheme (MDN Web Docs) (Mozilla)
- forced-colors (MDN Web Docs) (Mozilla)
- light-dark() (web-features explorer) (W3C WebDX Community Group)
- Forced colors (web-features explorer) (W3C WebDX Community Group)
- Page: emulateMedia (Playwright documentation) (Microsoft (Playwright))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress