Gesso

Tokens

Identity is --brand-*. Status and chrome are --sys-*.

Specimens live under Foundations. This page is architecture, install, and retune.

Components use utilities from bindings.css. A look retune fills --brand-* (primary, secondary, type, radius). System owns chrome and status (danger, success, warning, info). A prefab may override a status slot. Chrome (the neutral room — surfaces, ink, borders) stays achromatic until you pick a paper temperature or a warm prefab.

Primitives  →  Brand (identity)  →  Bindings
off-limits     primary, secondary, type, radius   components use these
            →  System (chrome + status + cat)  →  Bindings
               room, danger, success, --sys-cat-*

--cat-* are primitives (categorical data hues). --sys-cat-* adds --chrome-chroma-tag (the flat tag scalar, not the l-aware chrome curve) and keeps the hue, except gray, which follows the room. A brand retune does not touch them.

Want to try a retune? Open the brand studio.

Chrome temperature

Chrome is the neutral room: --sys-bg, --sys-surface*, --sys-fg*, --sys-border*. Brand color (--brand-primary, support, danger, etc.) sits on top. Paper temperature only tints that chrome — it does not recolor the brand.

Base, warm, and cool

The dry neutral ramp (--neutral-*) is achromatic (chroma 0). Temperature presets take each chrome role's lightness from that ramp and paint chroma + hue on top.

PaperKnobs a look setsWhat it does
Gray (base)none (omit)No tint. Surfaces read as neutral gray.
Warm--chrome-chroma: var(--warm-chroma) · --chrome-hue: var(--warm-hue)Taupe / Vellum warmth. Fitted to Figma neutrals/gray.
Cool--chrome-chroma: var(--cool-chroma) · --chrome-hue: var(--cool-hue)Blue-gray coolness. Fitted to slate on the same L ramp.

Washi and ICS are warm prefabs: data-theme="washi" / data-theme="ics" set those warm knobs for you. The sidebar's paper picker in these docs is host-only: it toggles data-chrome on the shell and app/global.css maps that to the knobs above. The installed kit responds to [data-theme], not data-chrome — that picker axis lives in the docs host.

The calc (both temperatures)

Warm and cool share one curve shape; only hue and peak chroma differ. l is the lightness of the underlying --chrome-* role (from the neutral ramp):

chroma(l) = base + peak × (1 − l) × l
sys-role   = oklch(from var(--chrome-role) l chroma(l) hue)
HueBasePeakTag chroma*
Warm72°0.00420.050.012
Cool230°0.00420.0480.012

* --warm-chroma-tag / --cool-chroma-tag — scalar average for --sys-cat-* (categorical chips). Chrome surfaces use the l-aware --warm-chroma / --cool-chroma calc instead.

Knobs (the --warm-* / --cool-* curves and their tag scalars) live in registry/dry/tokens/system.css. system.css also paints every --sys-* surface from --chrome-chroma / --chrome-hue, so a look only has to set those two knobs to tint the room — a warm prefab does this for you, or set them on your own [data-theme].

Try it

The sidebar paper picker sets data-chrome on the whole docs shell. The demo below is an isolated compare (ink primary, room only shifts). Ramp strips show a reference palette vs the calc on the dry L ramp.

Warm ramp (50 → 950)

Figma Vellum reference vs warm chrome on the dry achromatic L ramp.

Figma Vellum (reference)
Warm — C 0.0042 + 0.05(1−l)l, H 72
Cool ramp (50 → 950)

Slate blue-gray reference vs cool chrome on the same L ramp.

Slate (reference)
Cool — C 0.0042 + 0.048(1−l)l, H 230
Cooltag C · H

Body ink on canvas. Muted sits lower. Subtle is the faintest.

Surface
Raised + strong line
Sunken well
Field

Basetag C · H

Body ink on canvas. Muted sits lower. Subtle is the faintest.

Surface
Raised + strong line
Sunken well
Field

Warmtag C · H

Body ink on canvas. Muted sits lower. Subtle is the faintest.

Surface
Raised + strong line
Sunken well
Field

Cool · darktag C · H

Body ink on canvas. Muted sits lower. Subtle is the faintest.

Surface
Raised + strong line
Sunken well
Field

Base · darktag C · H

Body ink on canvas. Muted sits lower. Subtle is the faintest.

Surface
Raised + strong line
Sunken well
Field

Warm · darktag C · H

Body ink on canvas. Muted sits lower. Subtle is the faintest.

Surface
Raised + strong line
Sunken well
Field

Install

shadcn add @gesso/tokens

Import the token files from the CSS entry, after Tailwind, so @theme inline resolves.

@import "tailwindcss";
@import "./styles/dry/primitives.css";
@import "./styles/dry/brand.css";
@import "./styles/dry/system.css";
@import "./styles/dry/bindings.css";

Chrome is achromatic gray by default. Warm/cool paper comes from a prefab (shadcn add @gesso/washi or @gesso/ics), or set the knobs on your own look: [data-theme="acme"] { --chrome-chroma: var(--warm-chroma); --chrome-hue: var(--warm-hue); }.

brand.css wraps itself in @layer gesso.brand. Leave your remaps unlayered so they beat that invert even if they are imported first.

Retune (most consumers)

Stay on the kit ramps. Override color only, or type only, or both. Surfaces, ink, and lines stay with system. Leave the remap unlayered. Do not put --brand-* in gesso.brand. If you skip layers entirely, brand overrides must come after brand.css.

[data-theme="acme"] {
  --brand-primary: var(--blue-600);
  --brand-primary-fg: var(--neutral-50);
}

Then <html data-theme="acme">. Dark is .dark on the same tree and inverts chrome in system.css. The dry default primary and secondary are ink, so they invert too. A chromatic --brand-primary or --brand-secondary on [data-theme] does not.

Brand tokens (point each at a primitive, not another --brand-*):

GroupTokens
Primary--brand-primary, --brand-primary-fg, --brand-primary-subtle
Secondary--brand-secondary, --brand-secondary-fg, --brand-secondary-subtle
Type--brand-font-ui, --brand-font-body, --brand-font-display
Radius--brand-radius (boxes), --brand-radius-control (buttons)
Stroke--brand-font-weight-ui → derived --sys-strokeborder-stroke, font-ui

Each hue has a solid, a foreground, and a subtle tint (the old soft / emphasis pair). --brand-primary-subtle binds direct (→ bg-primary-subtle / border-primary-subtle); --brand-secondary-subtle seats in chrome via --sys-soft (→ bg-secondary-subtle), so its fill picks up paper temperature. The solid second hue is consumed as bg-support (shadcn owns secondary). Filled chip: bg-secondary-subtle + text-fg; outline: border-primary-subtle text-primary.

System status (point at a primitive; a prefab may override the slot). Ink is the 600 step (warning 500); muted fills are 50, and 950 in dark:

GroupTokens
Danger--sys-danger, --sys-danger-fg, --sys-danger-muted
Success--sys-success, --sys-success-fg, --sys-success-muted
Warning--sys-warning, --sys-warning-fg, --sys-warning-muted
Info--sys-info, --sys-info-fg, --sys-info-muted

shadcn's bg-secondary / bg-accent are aliases for the fill wash (bg-fill). The second brand hue is bg-support / text-support-fg. Kit chrome names and the alias table live on Color. Switch Washi or ICS in the sidebar to see blossom or orange land on support.

primarysupportdangersuccesswarninginfo

font-sans is UI (buttons, inputs, the shell). font-body is prose. font-display is titles. Bindings read --brand-font-* directly. font-mono is --sys-font-mono only; there is no brand mono. Sizes and leading are a named scale (text-display, text-heading-16, text-body-*, text-caption). Looks retune faces, not metrics. See Typography. Color roles: Color.

Radius: --radius-scale-* is the catalog brand picks from. --brand-radius is the box pick. The unsized token (rounded) is medium, so rounded-md is the same value. rounded-lg derives from it (+ 4px). --brand-radius-control is buttons only (rounded-control). Chips pin the catalog step --radius-scale-md in component markup so they do not inherit pill controls or ICS box radius. rounded-full stays a circle. Dry and Washi pick --radius-scale-md (8px) for box and control. ICS picks --radius-scale-full on control (pills) and --radius-scale-xl on boxes. Switch them in the sidebar. Bindings read --brand-radius* directly. Specimens: Radius.

Stroke: set --brand-font-weight-ui only — border-stroke derives from it (400 → 1px, 600 → 2px, clamped to the --stroke-1 / --stroke-2 catalog cap). Bindings expose border-stroke and @utility font-ui.

Surfaces stack; shadows use one non-inverting ink (shadow-sm / md / lg). See Elevation. Overlay motion is fade on poppers, scale (not transform) on centered dialogs, translate on sheets. See Motion.

These docs load Washi's faces as the host default: Hanken Grotesk on font-sans, Newsreader on .prose, Fraunces on headings (SOFT 4). An Atlas retune fills the same roles with Sora on font-sans and font-body (running UI and copy are one face) and Outfit on font-display. Atlas's own --font-sans token is Outfit, but that slot is headings there; do not copy the label.

Prefab (Washi)

Washi is warm chrome (self-contained in washi.css) plus wave/blossom on --brand-*, vermilion on --sys-danger, the three faces, and 8px corners. Load the font files in the host; the prefab only names them.

shadcn add @gesso/washi
@import "./styles/dry/themes/washi.css";

Unlayered, same as a retune: wave and blossom survive .dark even if this import sits before brand.css.

<html data-theme="washi">

Dark is .dark with data-theme="washi": lightness inverts, warmth and wave stay.

Prefab (ICS)

ICS is warm chrome plus Atlas v2 type plus violet and orange. The prefab defines --ics-violet-* and --ics-orange-* as primitives (off-limits, not bg-violet-300). Brand picks 600. Sora is font-sans and font-body. Outfit is font-display. Controls are pills (--brand-radius-control: var(--radius-scale-full)). Boxes are --radius-scale-xl. It keeps dry status; only the warm paper knobs and the chromatic-soft bypass touch --sys-*. Load Outfit and Sora in the host; the prefab only names the faces.

shadcn add @gesso/ics
@import "./styles/dry/themes/ics.css";
<html data-theme="ics">

Dark is .dark on the same tree with data-theme="ics": chrome lightness inverts; brand remaps to lighter violet primary, orange-600 soft, and violet-300 emphasis (see themes/ics.css).

On this page