One set of colours for light and dark
Published · Tagged css, dark-modeMost themes that support dark mode keep two copies of their colours: one set of variables for light, another for dark, swapped by a media query or a class on <html>. LunarCSS keeps one.
light-dark()
Each colour token holds both values at once:
:root {
color-scheme: light dark;
--lunar-bg: light-dark(var(--lunar-light), var(--lunar-dark));
--lunar-fg: light-dark(var(--lunar-dark), var(--lunar-light));
}
light-dark() returns its first value when the element’s color-scheme is light and its second when it’s dark. With color-scheme: light dark on the root, the browser picks from the reader’s system setting. No media query, no JavaScript and no second set of variables.
To force a mode, set data-theme="light" or data-theme="dark" on <html>. The theme turns that into a color-scheme, and every light-dark() value flips at once. color-scheme is inherited, so it works on any element: a dark aside on a light page is one attribute.
Two main colours
The whole palette hangs off two tokens, --lunar-light and --lunar-dark. The background and text colours swap between them, and the muted colour is mixed from both:
:root {
--lunar-muted-mix: 50%;
--lunar-muted: light-dark(
color-mix(
in oklch,
var(--lunar-light),
var(--lunar-dark) var(--lunar-muted-mix)
),
color-mix(
in oklch,
var(--lunar-dark),
var(--lunar-light) var(--lunar-muted-mix)
)
);
}
The mixing happens in the browser, not in Sass. So when you override the main colours, everything mixed from them follows:
:root {
--lunar-light: #faf9f7;
--lunar-dark: #1c1917;
--lunar-muted-mix: 65%;
}
That’s a warmer theme in three lines.
Why oklch
Every mix in the theme uses oklch. It’s a colour space built to match how bright colours look to people, so a 50% mix between two colours looks halfway between them. Mixing in srgb gives muddier, uneven results, which is why the theme never does.