Theming

Light and dark via data-theme on <html> — override semantic aliases, not primitives.

Theme contract

Dark is the default. Set data-theme="dark" or data-theme="light" on the document element. Semantic tokens under html[data-theme="light"] remap purpose aliases; primitive scales stay unchanged.

Use the theme toggle in the corner of this page to preview light and dark live — the same data-theme attribute your app should set. Recolor the whole system from Examples → Colors with --ca-hue.

Palette hue

Semantic color is OKLCH with a shared brand hue. Default is macOS-adjacent 254. Set --ca-hue and optionally --ca-chroma (a multiplier, default 1) on :root or html after Caustica CSS loads. --ca-chroma: 0 is Ink — grayscale brand, same light and dark surfaces. Status tokens start on the shipped ok, warn, danger, and info hues. They keep their own chroma so warnings still read on Ink. Retune with the examples meters or optional --ca-hue-ok, --ca-hue-warn, --ca-hue-danger, or --ca-hue-info.

The examples style guide paints these knobs live — wheel, system picker, screen eyedropper (other windows or another browser tab in Chromium), or a screenshot with movable pins on the pixels we sampled. Copy the CSS snippet from that panel into a host file imported after caustica-design/css. Same recipe on Getting started → Brand palette.

/* Host file imported after caustica-design/css */
:root {
  --ca-hue: 192;
  --ca-chroma: 1;
}

Glass knobs

Semantic glass reads three host knobs: --ca-blur-glass (frost, default 32px), --ca-glass-fill (surface alpha multiplier, default 1), and --ca-glass-edge (border / highlight / rim multiplier, default 1). Set them on :root or html after Caustica CSS loads. Fill and edge stay in family with light and dark recipes — they scale the shipped alphas, they do not replace them.

The examples style guide paints these meters live. Copy the CSS snippet from that panel into a host file imported after caustica-design/css. Same recipe on Getting started → Glass knobs.

/* Host file imported after caustica-design/css */
:root {
  --ca-blur-glass: 24px;
  --ca-glass-fill: 0.8;
  --ca-glass-edge: 1.1;
}

HTML attribute

<html lang="en" data-theme="dark">
  <!-- or data-theme="light" -->
</html>

Avoid a theme flash

Static multipage sites ship with a default data-theme in HTML. Restoring theme only after deferred JS runs paints dark first. Skeletons and spinners do not fix that. Put a tiny blocking script in <head> that sets data-theme synchronously. This docs site uses data-ca-theme-boot: it restores theme only after a consent choice, reading optional localStorage (Accept) or tab-only sessionStorage (Accept or Essential). See Cookies.

React helpers

useTheme, setTheme, getTheme, toggleTheme, and toggleThemeFromEvent live on caustica-design/core. They read and write document.documentElement’s data-theme.

import { setTheme, useTheme, toggleThemeFromEvent } from 'caustica-design/core'

function ThemeToggle() {
  const [theme] = useTheme()
  return (
    <button type="button" onClick={toggleThemeFromEvent}>
      {theme === 'dark' ? 'Light' : 'Dark'}
    </button>
  )
}

// Imperative (no animation)
setTheme('light', { immediate: true })

// Circular reveal from a point
setTheme('dark', { origin: { x: 24, y: 24 } })

Theme transition

On desktop, toggling theme runs a circular reveal from the control via the View Transitions API when available. The wipe uses a reused stylesheet with baked clip-path coordinates on ::view-transition-new(root). On phones and coarse-pointer devices (Chrome Android, Samsung Internet), the circular wipe is skipped — those engines pin the reveal to the top-left, hide fixed glass menus mid-transition, and stutter on full-page snapshots. Theme swaps immediately with the icon nudge only. CSS in motion.css:

  • Desktop: html[data-theme-to] marks the transition; JS injects literal clip-path keyframes from the toggle
  • Fallback (no VT): .is-theme-leaving / .is-theme-entering — opacity only (no shell transform)
  • Icon nudge: .is-theme-toggling on the toggle button
  • prefers-reduced-motion: reduce and mobile / coarse pointer skip the wipe

Try the theme toggle in the corner — circular reveal on desktop; instant swap on phones.

Foundation vs full CSS

Theme tokens ship with both CSS entries. Pick the surface your host needs:

  • caustica-design/css/foundation — fonts, token layers, and body chrome (no component classes)
  • caustica-design/css — foundation plus buttons, panels, pickers, and the rest

Importing only token files without foundation/base drops body font, canvas glow, and grain. For brand remaps, see Tokens → override.

Wrap the host UI in .ca-root. Prefer fewer stacked backdrop-filter layers; dense screens can lower --ca-blur-glass / --glass-blur, or drag the meters in the examples glass studio.

import 'caustica-design/css/foundation'
// or full system:
import 'caustica-design/css'