Skip to content

Search

Start typing to search.


Show all pages

Documentation

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:

CSS
: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
<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
<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:

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

Calaca tokens with the values of the base skin
TokenGroupBase, lightBase, dark
--ca-surface-pageSurfaceoklch(98.5% 0 0)oklch(14.5% 0 0)
--ca-surface-cardSurfaceoklch(100% 0 0)oklch(20% 0 0)
--ca-surface-mutedSurfaceoklch(96% 0 0)oklch(26% 0 0)
--ca-surface-inverseSurfaceoklch(15% 0 0)oklch(100% 0 0)
--ca-text-primaryTextoklch(15% 0 0)oklch(100% 0 0)
--ca-text-secondaryTextoklch(44% 0 0)oklch(72% 0 0)
--ca-text-on-actionTextoklch(100% 0 0)oklch(15% 0 0)
--ca-text-on-secondaryTextoklch(15% 0 0)oklch(100% 0 0)
--ca-text-on-inverseTextoklch(100% 0 0)oklch(15% 0 0)
--ca-border-defaultBorderoklch(88% 0 0)oklch(30% 0 0)
--ca-border-strongBorderoklch(55% 0 0)oklch(60% 0 0)
--ca-focus-ringFocusoklch(44% 0 0)oklch(70% 0 0)
--ca-action-primaryActionoklch(15% 0 0)oklch(100% 0 0)
--ca-action-primary-hoverActionoklch(27% 0 0)oklch(85% 0 0)
--ca-action-secondaryActionoklch(93% 0 0)oklch(30% 0 0)
--ca-status-successStatusoklch(48% 0.12 155)oklch(74% 0.14 155)
--ca-status-warningStatusoklch(50% 0.12 65)oklch(80% 0.14 75)
--ca-status-errorStatusoklch(52% 0.2 25)oklch(72% 0.16 25)
--ca-status-infoStatusoklch(48% 0.13 250)oklch(74% 0.12 250)
--ca-overlayOverlayoklch(0% 0 0 / 0.4)oklch(0% 0 0 / 0.6)
--ca-radius-controlShape0.5remSame as light
--ca-radius-cardShape0.75remSame as light
--ca-radius-shellShapeSame as lightSame as light
--ca-font-sansTypeui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serifSame as light
--ca-font-displayTypeui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serifSame as light
--ca-font-monoTypeui-monospace, "SFMono-Regular", Menlo, Consolas, monospaceSame as light
--ca-type-xsTypeSame as lightSame as light
--ca-type-smTypeSame as lightSame as light
--ca-type-baseTypeSame as lightSame as light
--ca-type-lgTypeSame as lightSame as light
--ca-type-xlTypeSame as lightSame as light
--ca-type-2xlTypeSame as lightSame as light
--ca-type-3xlTypeSame as lightSame as light
--ca-type-4xlTypeSame as lightSame as light
--ca-type-5xlTypeSame as lightSame as light
--ca-type-6xlTypeSame as lightSame as light
--ca-type-7xlTypeSame as lightSame as light
--ca-type-8xlTypeSame as lightSame as light
--ca-type-9xlTypeSame as lightSame as light
--ca-weight-mediumTypeSame as lightSame as light
--ca-weight-semiboldTypeSame as lightSame as light
--ca-weight-boldTypeSame as lightSame as light
--ca-weight-blackTypeSame as lightSame as light
--ca-leading-tightTypeSame as lightSame as light
--ca-leading-snugTypeSame as lightSame as light
--ca-leading-normalTypeSame as lightSame as light
--ca-leading-relaxedTypeSame as lightSame as light
--ca-leading-looseTypeSame as lightSame as light
--ca-tracking-tighterTypeSame as lightSame as light
--ca-tracking-tightTypeSame as lightSame as light
--ca-tracking-normalTypeSame as lightSame as light
--ca-tracking-wideTypeSame as lightSame as light
--ca-tracking-widerTypeSame as lightSame as light
--ca-tracking-widestTypeSame as lightSame as light
--ca-tracking-capsTypeSame as lightSame as light
--ca-shadowElevationSame as lightSame as light
--ca-shadow-2xsElevationSame as lightSame as light
--ca-shadow-xsElevationSame as lightSame as light
--ca-shadow-smElevationSame as lightSame as light
--ca-shadow-mdElevationSame as lightSame as light
--ca-shadow-lgElevationSame as lightSame as light
--ca-shadow-xlElevationSame as lightSame as light
--ca-shadow-2xlElevationSame as lightSame as light
--ca-shadow-strongElevationSame as lightSame as light
--ca-easeMotionSame as lightSame as light
--ca-ease-emphasisMotionSame as lightSame as light
--ca-durationMotionSame as lightSame as light
--ca-densitySpacingSame as lightSame as light