A few ways of specifying per-theme colours in only CSS

birdculture1 pts0 comments

A few ways of specifying per-theme colours in only CSS — Chris Morgan A few ways of specifying per-theme colours in only CSS<br>🗓️ 2026-05-16 • Tagged /css, /meta=also<br>Table of contents:<br>The assumed basic HTML and CSS Write it all out the hard way (doesn’t scale very well) Lots of colour palette variables (what normal people do) color-mix() with one variable per theme (I settled on this for this site, but I’m weird) light-dark() (consider using this these days, instead of or combined with palette variables) if() (maybe some time soon) Paused @keyframes animation (probably a bad idea) Summary (a comparison table) I was thinking about this as part of putting this website together. Actually it was because I forgot about light-dark(); if I’d remembered that earlier I probably wouldn’t have ended up with all this! My requirements: (which may not match your requirements) Must support auto (based on prefers-color-scheme), light, and dark, chosen by radio buttons. Must work without any JavaScript (persistence is out of scope). The assumed basic HTML and CSS<br>Up to you exactly how to include and structure it, but all the examples that follow assume something like this:<br>Theme<br>radio name =theme id =theme-auto checked> Follow system<br>radio name =theme id =theme-light> Light<br>radio name =theme id =theme-dark> Dark<br>I’m going to assume the availability of a few CSS features: @media (prefers-color-scheme: dark) for automatic selection. It shipped in 2019–2020, which is generally enough. :has() for manual selection without needing additional JavaScript. It’s newer, supported back to 2023-12; I consider that enough to rely on in general, but if you’re not happy with it, you have two options: Only support one behaviour (probably auto) sans-JS. This is a very reasonable choice. You might add a light or dark class to the root element. Shift the radio buttons to be direct children of the body, hiding them visually, and then target #theme-foo:checked ~ * … instead of :root:has(#theme-foo:checked) …. Messy and with some consequences and inconveniences, but generally possible. I will also use nested selectors in some places in this article (2023-12); but they’re easily flattened if you wish.<br>Now to the five techniques. 1. Write it all out the hard way<br>Old-school and verbose. The most compatible. It’s easy to see how to add more themes than just light and dark. Syntax is verbose. Dark theme value has to be repeated. some-element {<br>color: darkred;

:root:has(#theme-dark:checked) & {<br>color: pink;

@media (prefers-color-scheme: dark) {<br>:root:has(#theme-auto:checked) & {<br>color: pink;<br>} It’s often structured differently, with the nesting effectively inverted as in the next example, but that’s the gist of it.<br>2. Lots of colour palette variables<br>Probably the most popular approach, traditionally. Though I was surprised to realise the implementations only landed in 2014–2017. Sure feels longer ago than that. But it still feels okay to call it “traditional”, because people often used Sass variables to similar effect before. (You didn’t often have multiple themes in those days.) Works since 2017-04. It’s easy to see how to add more themes than just light and dark. Use-site syntax is ideal. Separating colour definitions from their use location may help or hinder, and may or may not fit nicely into your project. It can encourage some bad and some good patterns, and may yield a little more overhead. (I’m being vague deliberately, and will not elaborate here.) Dark theme value has to be repeated. The declaration: :root {<br>--color-somepurpose: darkred;<br>--color-another: …;

:root:has(#theme-dark:checked) {<br>--color-somepurpose: pink;<br>--color-another: …;

@media (prefers-color-scheme: dark) {<br>:root:has(#theme-auto:checked) {<br>--color-somepurpose: pink;<br>--color-another: …;<br>} Adding more themes is trivial. And use site: some-element {<br>color: var(--color-somepurpose);<br>} You can also obviously use variables for the next three approaches.<br>3. color-mix() with one variable per theme<br>I don’t recall encountering this technique before (actually I’ve been surprised at how little attention color-mix() has been paid), but it works. Basically, define a colour as a mixture of all the colours across all themes, but use a variable to set one of the themes to 100% and the rest to 0%. (In practice, you’re normally just using two themes: light and dark.) Works since 2023-05. It’s easy to see how to add more themes than just light and dark. Syntax is reasonably compact. Syntax is likely to be unfamiliar, but is fairly straightforward. Pity about the interpolation method part that’s currently necessary. Opens up interesting ideas for actually mixing colours/themes. What does --dark: 50%; mean? You can play around with the consequences 🎨 Aa on this site! If you do try mixing colours and themes, controlling interpolation is a pain; you have to figure out how to express your desired function mathematically. This is the technique I settled on for my own site, because once I realised...

theme color dark light themes checked

Related Articles