Theming
Tokens carry the values, skins and brands supply them, and data attributes switch skin and appearance.
Tokens
Components never read a color, radius or font directly. They read tokens:
CSS custom properties that start with --ca-. A skin gives every token a value
for light and dark, and a brand stylesheet can change any of them.
Tokens are named by role, not by look: --ca-action-primary is the color of
the main action, whatever that color is. The full list is at the end of this
page.
Inside Tailwind, the tokens are also available as theme colors, so utilities
such as bg-primary, text-muted-foreground and border-input follow the
skin.
Brands
A brand changes values, not shape. Put the values in a plain CSS file after the Calaca stylesheet:
:root {
--ca-action-primary: oklch(52.92% 0.1104 163.97);
--ca-action-primary-hover: oklch(59.13% 0.1231 164.29);
--ca-text-on-action: oklch(100% 0 0);
}Skins declare their tokens with low specificity (:where(:root)), so a brand
rule on :root always wins, whatever the order of the files. When a brand
needs a different shape, density or type, it needs its own skin.
Light and dark
Without any attribute, Calaca follows the operating system. To pin an
appearance, set data-appearance on <html>:
<html lang="en" data-appearance="dark">data-appearance="light" pins light, data-appearance="dark" pins dark, and
no attribute means “follow the system”. Dark values only apply on screens, so
printed pages stay light.
On pages that also wear a Silvfox brand (--sf-* tokens), data-theme is the
same pin’s older name: it still works, and data-appearance wins when both
are set. ca-appearance.js mirrors the appearance onto data-theme, so one
control flips the whole mixed page.
In your own Tailwind classes, the dark: variant follows the same rule.
Switching skins
ca-all.css (or @calaca/ui/skins/all.css in a Tailwind build) holds every
built-in skin. Name the skin with data-skin, on <html> for the whole page
or on any element for one region:
<html lang="en" data-skin="calaca">
…
<section data-skin="proof">
<button class="ca-button ca-button--primary ca-button--md" type="button">Save changes</button>
</section>
</html>An element follows the nearest data-skin around it. Outside any skin, an
element shows the core alone: the skeleton, with structure and behavior but no
clothes. One limit: the same skin as an outer and an inner region around
another skin (base around proof around base) resolves the innermost part to
the bare core.
Remembering the choice
ca-appearance.js remembers the visitor’s skin and appearance and applies them
before the first paint, so the page never flashes. Load it as a classic script
in the <head>, then mark the controls with data-ca-control:
<label for="appearance">Appearance</label>
<select id="appearance" data-ca-control="appearance">
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>data-ca-control="skin" works the same way for skins. When the visitor has not
chosen anything, the script leaves the attributes your page sets alone.
Token reference
The values below come from the base skin, the neutral starting point.