Skip to main content
Gremorie
Semantic

Overview

Semantic tokens translate primitives into intent - how Gremorie supports multiple themes and dark mode without components knowing.

If primitives are "what colors exist", semantic tokens are "what those colors mean". Instead of painting a button as bg-gray-900, you paint it as bg-primary, and the meaning of "primary" changes when the theme changes.

This is layer 2 of 3: it consumes primitives and is what components (and the chart layer) consume in turn. The full picture lives in the three-layer model.

Intent as the interface

All the chrome of Gremorie components talks to the semantic layer. It defines a fixed set of intents, background, foreground, primary, border, ring, and so on, that every theme must fill.

IntentWhat it represents
background / foregroundBase surface and the text on top of it
card / popoverSurface variations with elevation
primaryThe main action - the protagonist button
secondarySecondary action, low contrast
mutedBackground-level text and surfaces
accentHover, selection, subtle highlight
destructiveIrreversible actions, errors
warning / successAttention and ok status fills
border / input / ringForms and focus state
chart-cat-1 ... -5Categorical series (see Chart tokens)
sidebar-*Variants for the navigation chrome

Seven themes, two modes

Gremorie ships seven themes. The Default theme is the base layer itself; five match the visual voice of the leading AI products, and Purple is a violet theme for reading products, adapted from the Fumadocs Purple preset:

Default

The neutral gray base layer. Conservative, professional, no brand identity.

Claude

Warm clay (coral terracotta) on cream surfaces, taupe support, serif display.

ChatGPT

Emerald accent on a flat, low-shadow graphite surface.

Gemini

Google blue with a blue-to-purple chart ramp and the roundest radius.

Perplexity

True turquoise on offblack. Sober, tight, the smallest radius.

Mistral

Flame orange with a black-on-orange primary, straight from the brand.

Purple

Violet on lavender surfaces, adapted from the Fumadocs Purple preset, with WCAG AA corrections.

Each theme ships light and dark independently. Switching is a single attribute change:

<html data-theme="claude">
  <!-- Claude, light -->
</html>
<html data-theme="gemini" class="dark">
  <!-- Gemini, dark -->
</html>

Components do not know which theme is active. They ask for bg-primary, and the browser resolves var(--primary) in the right context.

How the tokens are defined

The source is DTCG JSON in packages/tokens/tokens/ (semantic/light.json, semantic/dark.json, themes/<id>.json), which generates packages/tokens/styles/theme.css (:root for light, .dark for dark) and themes/<id>.css. Each named theme is an override file that remaps only what differs, naming primitives by their token path:

{
  "light": {
    "primary": { "$type": "color", "$value": "{color.orange.600}" },
    "primary-foreground": { "$type": "color", "$value": "{color.gray.950}" },
    "accent": { "$type": "color", "$value": "{color.orange.100}" }
  }
}

The build writes each alias as a reference to the primitive, so the generated CSS keeps the cascade:

:root[data-theme='mistral'] {
  --primary: var(--color-orange-600);
  --primary-foreground: var(--color-gray-950);
  --accent: var(--color-orange-100);
  /* ... */
}

The consequence: adjust --color-orange-600 in the primitive layer and the Mistral theme, along with any other theme referencing orange-600, updates automatically. Edit once, it cascades. Tokens a theme does not override resolve to the base layer, so an accent theme like Mistral stays a dozen lines while a full-surface theme like Claude remaps almost everything.

For the full table

The Colors page has the intent-to-value mapping (light and dark) for the seven themes. Use it to understand why a button turns clay in one theme and emerald in another, and to audit whether your own custom theme is filling the intents with consistent values.

The rule of thumb: reference primitives by alias ({color.orange.600}, written to CSS as var(--color-orange-600)) whenever one fits, so the cascade stays intact. The shipped themes hardcode OKLCH only for product-matched surfaces that have no primitive equivalent.

On this page