Using Espalier Tokens, Intents, And Contexts Coherently

Espalier gives application authors three different styling levers:

Lever Use it when Typical examples
Theme configuration on <esp-root> The whole product should change together. seedColor, semanticMappings, typeRatio, spaceRatio, font families
Component-level CSS properties A specific component instance needs a local override. --esp-tooltip-max-width, --esp-progress-height, --esp-header-background
intent on a component The element carries meaning — success, warning, danger, info. intent="success", intent="danger"
context on a container A zone of the page uses a theme-defined rebinding — the inverted footer, the quiet band. context="inverted"

When these are used in the right order, the system stays coherent. When they are mixed casually, the result is usually brittle CSS and inconsistent color semantics.

Start With Semantic Tokens

Prefer semantic tokens such as --esp-color-text, --esp-color-border, --esp-color-action-background, and --esp-color-link over raw variant colors such as --esp-color-complementary or --esp-color-triadic-left.

Semantic tokens answer questions like "what is the text color here?" or "what is the action background here?" Raw variant tokens answer "which hue family are we using?" In application code, the semantic question is usually the one you want.

Use raw variant colors when you are:

  • configuring the theme itself
  • building a new semantic mapping
  • creating a deliberate accent effect that is not part of a component's normal semantic contract

Keep Sizing Font-Relative

Espalier sizing is designed to scale with typography. Prefer these token families:

  • --esp-type-* for text scale
  • --esp-size-* for spacing
  • --esp-size-font, --esp-size-padding, and --esp-size-padding-page for common aliases
  • rem and em for custom measurements that should grow with the system

Avoid hard-coded pixel values for layout, spacing, and typography unless you are dealing with a genuinely fixed physical detail such as a 1px hairline, a bitmap asset boundary, or a very small icon stroke.

Understand What intent And context Change

Espalier 3 splits the retired variant attribute into two attributes that carry its two unrelated jobs:

intent states what an element meansneutral, success, warning, danger, or info. On token-emitting controls such as buttons, a non-neutral intent pins the element's filled-action pair (--esp-color-action-background / --esp-color-action-text) to the intent's own fixed status family — red-orange, green, yellow-green, and blue by default, each retunable per theme via intents — with contrast enforced against the actual action surface. Class-styled chrome (esp-badge, esp-info, esp-status-indicator) renders its intent treatment from CSS classes instead and emits no inline tokens. An intent never repaints the element's surroundings: a danger button on a cream card stays on the cream card.

<esp-button intent="danger" label="Delete forever"></esp-button>
<esp-badge intent="success">Published</esp-badge>

context selects a theme-defined zone — the inverted footer, the quiet band — and re-emits the complete semantic table for that subtree, re-derived and contrast-enforced against the zone's own surfaces. Zones are declared in the theme; see the theming guide.

<esp-box context="inverted">
  <h3>Reversed section</h3>
  <p>Zone-correct tokens for every descendant, slotted HTML included.</p>
  <esp-button intent="danger" label="Delete"></esp-button>
</esp-box>

The two compose: an intent inside a context keeps its status hue and its contrast against the zone's surfaces. Use intent for meaning, context for zones, and neither for an isolated style tweak.

Change Component Props Before Local Root Tokens

If a component already exposes a component-level CSS property, prefer that property over overriding a root semantic token inside the component subtree.

Good:

.upload-progress {
  --esp-progress-fill-color: var(--esp-color-success);
}

Avoid:

.upload-progress {
  --esp-color-action-background: var(--esp-color-success);
}

The first change is local and explicit. The second changes a semantic root token for every descendant, which can accidentally affect text, links, nested components, or future styling additions.

Change The Theme When The Language Should Shift Everywhere

If multiple components need the same new visual language, change the theme rather than stacking many CSS overrides.

Good theme-level changes include:

  • changing the seed color
  • adjusting semanticMappings
  • tightening or loosening chroma ranges
  • changing typeRatio, spaceRatio, or font families
  • setting shared background-image fields for pages, boxes, or vellum overlays
const lightTheme = {
  seedColor: "oklch(0.68 0.14 210)",
  typeRatio: 1.2,
  spaceRatio: 1.5,
  semanticMappings: {
    link: { source: "triadic-left", lightness: "accent" },
  },
};

This keeps the whole system internally consistent. It is usually better than overriding a scattered list of emitted --esp-color-* outputs in CSS.

When You Touch semanticMappings, Think In Groups

semanticMappings is the engine's low-level contract, but product theming should usually move coherent groups together rather than editing isolated tokens one by one.

Start with the grouped model from Semantic Color Groups:

  • foundation surfaces
  • structure and chrome
  • content ink and hierarchy
  • action surfaces
  • inline emphasis and feedback
  • utility and status

That model helps you decide which mapping choices are safe to expose in an application editor and which ones should remain internal to Espalier's contrast and state logic.

Good Patterns And Brittle Patterns

Prefer Avoid
var(--esp-size-small) or var(--esp-type-medium) Literal 14px, 22px, or large spacing values copied from another app
intent="success" on a status-bearing control Manually setting several --esp-color-* properties on one component to fake a status treatment
Component-prefixed props such as --esp-tooltip-max-width Inventing new generic globals such as --esp-color-text-muted for one component
Theme config changes on <esp-root> for shared brand shifts Re-theming the app by overriding many emitted root tokens one by one
Semantic colors such as --esp-color-text and --esp-color-border Raw variant colors for ordinary body text, borders, and form surfaces

Guidance For AI Authors

If you are generating Espalier code with AI, these defaults produce the least brittle output:

  • Prefer existing Espalier tokens over literal colors and measurements whenever a matching token family exists.
  • Prefer semantic colors over raw variant colors in application CSS.
  • Prefer a theme-defined context when a whole subtree should shift palettes together, and intent for an element's status.
  • Prefer component-level custom properties over locally overriding --esp-color-* outputs.
  • Prefer theme configuration on <esp-root> when the desired change should apply across the product.
  • If a component lacks the styling hook you need, call that out as a library gap instead of inventing an undocumented global token.
  • Do not target --_esp-* tokens. Those are private implementation details, not supported application-level hooks.

That last rule matters. Undocumented globals feel convenient in the short term, but they make future theming and component evolution much harder.

Components API Guides Getting started Styling Espalier Browser support GitHub npm package Taproot I/O