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.
| Intent | What it represents |
|---|---|
background / foreground | Base surface and the text on top of it |
card / popover | Surface variations with elevation |
primary | The main action - the protagonist button |
secondary | Secondary action, low contrast |
muted | Background-level text and surfaces |
accent | Hover, selection, subtle highlight |
destructive | Irreversible actions, errors |
warning / success | Attention and ok status fills |
border / input / ring | Forms and focus state |
chart-cat-1 ... -5 | Categorical 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.