CSS Color Theming
For a while now I've been pondering just how I'd approach theming a website in a way that's easy to understand, supports varied colors on the same page, and supports light and dark modes.
Starting from scratch with this simple vanilla HTML site was the perfect chance to bring my ideas together. And here's the result:
Theme: Dune
Base Color Scheme - Dust of AgesAsh Silence
Control is not maintained through force, but through the careful illusion that choice was ever an option. The mind, when cornered, will conjure gods or monsters—whichever best explains its surrender.
Color Scheme 01 - Spice BloomDeep Spice Vein
Thought is a weapon sharpened by solitude and dulled by consensus. The ritual of power is not in its exercise, but in the quiet calculus behind its withholding.
Color Scheme 02 - Sietch WhisperSietch Dusk
Flesh remembers what memory cannot, encoding fear and desire beneath the surface of posture. To name a thing is to begin the slow violence of shaping it into usefulness.
Color Scheme 03 - Mentat GreyCerebral Static
Time is the only true predator, and we feed it our certainty to delay its bite. A system survives not by truth, but by the elegance of its deceptions.
Color Scheme 04 - Sunshield MirageDesert Eclipse
Emotion is the syntax of the soul, and every silence carries its own grammar. Those who measure reality find themselves shaped by the tools they wield.
Goals:
- CSS only (no Javascript)
- Support light and dark modes — both system default and user-toggleable
- Support mutiple color schemes within a theme on the same page
- Support easily switching to different theme
- Support applying themes to just a part of the page
- Custom property api for theming elements within a theme / scheme
Here's what I ended up with:
-
Establish CSS layers
By establishing a layer order, I know that the theme styles will win because they're in a later layer.
-
Expose themeable properties
Mostly within pattern classes, declare properties that should be contollable based on theme context. (e.g.
body { background-color: var( --background-color ); }
)I use a `p-colored` class that applies basic background and color classes from the theme.
-
Declare default scheme
on the :root element, set
color-scheme: light-dark;
to respect system scheme.in the color theme styles, we'll use the
light-dark()
CSS function to apply the scheme. (e.g.--background-color: light-dark( var( --light-color ), var( --dark-color ) )
); -
Add a color mode toggle
Give the user a toggle for light or dark mode, by adding a checkbox (e.g.
<input type="checkbox" id="color-mode">
)This was the tricky part that took me a few tries to get right. We use the
has()
CSS selector, in coordination with color mode queries to let the user toggle to the oposite of the default system mode based on the input.@media (prefers-color-scheme: light) { body:has(input#color-mode:checked) { color-scheme: dark; } } @media (prefers-color-scheme: dark) { body:has(input#color-mode:checked) { color-scheme: light; } }
So if the user defaults to light mode, we toggle to dark; if they default to dark mode, we toggle to light.
-
Color theme naming and organization
Prefix theme related classes with
t-
so they're easy to identify.Theme Base
Each theme has a base class —
t-themename
In
:where(.t-themename)
establish the color palette with named custom properties, then apply pallete colors to themeable properties, usinglight-dark()
for each color assignment.The theme decides if colors are color-mode dependent, or if they stay the same.
Color Schemes
Theme can have multiple nested color schemes that serve different purposes.
Each color scheme uses a theme-independent generic class name (e.g.
t-scheme-a
,t-scheme-b
,t-scheme-c
) that can carry across multiple themes.in
.t-scheme {}
styles, override some or all of the themeable properties, usinglight-dark()
as needed to respect the user modes.Pattern APIs
Other CSS layers, such as the "patterns" layer, expose themeable custom properties (e.g.
--button-color
) that can be set per-scheme.One important pattern class is
p-colored
(I'm still debating how to name this) which applies background color and text color to an area of the page.
Notes:
- I asked ChatGPT to generate Dune-themed colors and color names.
- I asked ChatGPT to generate filler text in the style of Frank Herbert (but without any direct references to Dune)