Color, backgrounds and dark mode
Color notations including oklch, gradients and background layers, shadows versus borders, and a dark mode built on tokens.
Color notations that communicate intent
Every color notation can express the same pixel, so choose the one whose numbers mean something to a human. Hex is opaque; oklch exposes perceived lightness and hue, which makes it possible to adjust a palette deliberately.
| Notation | Example | When it helps |
|---|---|---|
| Hex | #2f6fed | Copying from a design tool, short and familiar |
rgb() / rgba() | rgb(47 111 237 / 0.6) | Transparency without changing notation |
hsl() | hsl(220 76% 56%) | Quick hue rotation, but lightness is perceptually uneven |
oklch() | oklch(56% 0.17 258) | Building ramps: same L looks equally light across hues |
color-mix() | color-mix(in oklch, var(--brand) 15%, white) | Deriving tints and shades from one token |
:root {
--brand: oklch(56% 0.17 258);
--brand-soft: color-mix(in oklch, var(--brand) 15%, white);
--brand-text: oklch(38% 0.14 258);
}Because lightness in oklch is perceptual, a ramp built by changing only that channel looks even, while the same ramp in HSL tends to bunch up in the greens and blues.
Background layers, shadows and focus rings
The background shorthand accepts several comma-separated layers painted back to front, so an overlay gradient and a photograph are two layers of one declaration rather than two elements.
.hero {
background:
linear-gradient(180deg, rgb(0 0 0 / 0.55), rgb(0 0 0 / 0.1)),
url("/img/hero.jpg") center / cover no-repeat;
}
.btn {
border: 1px solid transparent;
box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
}
.btn:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
}- The first layer listed paints on top; the last is the backdrop.
- Gradients are images, so they obey
background-sizeandbackground-positionlike any other layer. - Use the shorthand carefully — it resets every background sub-property you leave out.
- A border is part of the box and affects layout; a shadow sits outside it and does not.
- Use
outlinefor focus rings: it never affects layout and follows the element's shape.
Dark mode without a second stylesheet
color-scheme: light dark tells the browser your page handles both themes, so form controls, scrollbars and the area behind the page match instead of staying stubbornly white.
:root {
color-scheme: light dark;
--surface: white;
--text: #16181d;
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #17181c;
--text: #e9e9ec;
}
}
body { background: var(--surface); color: var(--text); }Override the same variables inside a [data-theme="dark"] rule to support an explicit user toggle. Because every component reads the variables, a toggle changes one attribute and the whole page follows.
FAQ
Should a theme toggle be built in JavaScript?
data-theme attribute override it, and set that attribute from an inline script in the head so the page never flashes the wrong theme.Why do my gradients look banded?
oklch, and add a very subtle noise texture if the banding is still visible.Related
Typography, fonts and fluid type CSS architecture, naming and design tokens
Last refreshed 2026-09-18.