✈️ 🧱 🔬

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:

  1. Establish CSS layers

    By establishing a layer order, I know that the theme styles will win because they're in a later layer.

  2. 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.

  3. 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 ) ));

  4. 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.

  5. 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, using light-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, using light-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: