color-scheme
color-scheme tells the browser and the operating system which colour schemes your site supports: light, dark, or both. When you declare support for both, the browser adjusts form elements, scrollbars, and default colours to match the user's system preference. Combined with media queries (prefers-color-scheme), it provides the foundation for building modern dark mode support.
The color-scheme property
/* Declare support for light mode only */
color-scheme: light;
/* Declare support for dark mode only */
color-scheme: dark;
/* Support both — browser/OS chooses based on user preference */
color-scheme: light dark;
/* Support both, with dark as preferred default */
color-scheme: dark light;
/* Global setting — usually on root element */
:root {
color-scheme: light dark;
}What color-scheme controls
When you declare color-scheme, the browser automatically styles several things to match:
- Form inputs, buttons, selects — background and text colours adjust
- Scrollbars — colours change to match the scheme
- Text input cursor — changes to be visible against the background
- Links — default colours may adjust (though custom colours take precedence)
- System default colours — the browser uses appropriate colours for the scheme
/* Without color-scheme — form inputs might not adapt */
body {
/* no color-scheme declaration */
background: #1a1a1a;
color: #ffffff;
}
/* Form input might have dark text on dark background — unreadable! */
input {
/* browser defaults may not adapt */
}
/* With color-scheme — form inputs automatically adapt */
:root {
color-scheme: light dark; /* browser knows to adapt form elements */
}
body {
background: #1a1a1a;
color: #ffffff;
}
/* Form input background and text automatically adjust for readability */
input {
/* browser provides appropriate scheme-aware defaults */
}Detecting user preference: prefers-color-scheme
Use the prefers-color-scheme media query to detect whether the user prefers light or dark mode (from their OS settings).
/* Light mode — default, usually */
:root {
--text-color: #000000;
--bg-color: #ffffff;
color-scheme: light;
}
/* Detect dark mode preference */
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--text-color: #ffffff;
--bg-color: #1a1a1a;
}
}
body {
color: var(--text-color);
background-color: var(--bg-color);
}Building a complete dark mode
/* Define colour palette in light mode */
:root {
--text-primary: #000000;
--text-secondary: #666666;
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--border-color: #dddddd;
--accent-color: #0066cc;
color-scheme: light;
}
/* Override for dark mode */
@media (prefers-color-scheme: dark) {
:root {
--text-primary: #ffffff;
--text-secondary: #aaaaaa;
--bg-primary: #1a1a1a;
--bg-secondary: #2a2a2a;
--border-color: #444444;
--accent-color: #4da6ff;
color-scheme: dark;
}
}
/* Use variables throughout — they adapt automatically */
body {
color: var(--text-primary);
background-color: var(--bg-primary);
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
color: var(--text-primary);
}
.button {
background-color: var(--accent-color);
color: var(--text-primary);
}
input {
background-color: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}Manual dark mode toggle (without OS preference)
You can also support manual theme switching using a class or data attribute, combining it with the OS preference as a fallback.
/* Light mode — default */
:root {
--text-color: #000000;
--bg-color: #ffffff;
color-scheme: light;
}
/* Manual dark mode class */
[data-theme="dark"] {
--text-color: #ffffff;
--bg-color: #1a1a1a;
color-scheme: dark;
}
/* OS prefers dark, but no manual override */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--text-color: #ffffff;
--bg-color: #1a1a1a;
color-scheme: dark;
}
}
body {
color: var(--text-color);
background-color: var(--bg-color);
}// Toggle dark mode manually
const toggle = document.querySelector('.theme-toggle');
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
// Persist to localStorage
localStorage.setItem('theme', next);
});
// On page load, restore saved preference
const saved = localStorage.getItem('theme');
if (saved) {
document.documentElement.setAttribute('data-theme', saved);
}Form elements and color-scheme
When you declare color-scheme, form inputs automatically get readable default colours. This is especially useful for <input>, <select>, <textarea>, and system buttons.
/* Before color-scheme — form inputs may look wrong in dark backgrounds */
body.dark-mode {
background: #1a1a1a;
color: #ffffff;
/* form inputs still have light backgrounds, text is hard to read */
}
/* After color-scheme — form inputs adapt */
:root {
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
}
}
body {
background: #1a1a1a;
color: #ffffff;
}
input, textarea, select {
/* Now have dark backgrounds with light text when color-scheme: dark */
}Combining with other features
/* Advanced: custom colours + OS preference */
:root {
/* Lightness scale using oklch() */
--brand-hue: 264;
--brand-lightness: 0.55;
--brand-color: oklch(var(--brand-lightness) 0.2 var(--brand-hue));
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
--brand-lightness: 0.65; /* lighter in dark mode */
color-scheme: dark;
}
}
.button {
background-color: var(--brand-color);
/* Colour automatically adjusts for dark mode */
}
/* High contrast mode support */
@media (prefers-contrast: more) {
:root {
--border-width: 2px;
--text-secondary: var(--text-primary);
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}