Foundations
Tokens
Every token group with its values in both modes, how token names are built, where tokens.json lives and how foundation.css is generated from it, and how to move off the deprecated names.
A token is a named value: a color, a shadow, a size. Components and products use the name, never the value, so a value can change in one place and every screen follows in both modes. This page is the reference for every token. The rules for choosing between tokens belong to the foundation that owns each group, linked from each section below.
Principles
- One source. Every token in
tokens.jsonis written once there.foundation.cssis generated from it, and nothing else defines a token value. - Names say the role, not the color.
attention, not pink;layer-canvas, not white. A token keeps its name when its value changes, and the dark value lives under the same name. - Use the role token before the palette. Reach for
palette-*only where no role fits; see Color. - A value used three times becomes a token. A one-off value can stay inline, as an ink step or an
oklch()literal; on its third use, add a token.
Where tokens live
| File | What it holds |
|---|---|
packages/ui/src/tokens.json | The source for colors, shadows, the focus ring and the metric tokens, in the Design Tokens Community Group format (2025.10). Published as @botiverse/raft-ui/tokens.json for tools outside CSS. |
packages/ui/src/foundation.css | Generated from tokens.json: every token as a CSS custom property, for light mode, dark mode and the system setting. Published as @botiverse/raft-ui/foundation.css for plain CSS. |
packages/ui/src/styles.css | The Tailwind entry. It imports foundation.css, maps tokens to utilities (bg-layer-canvas, text-foreground-muted, shadow-float) and defines the tokens that exist only as Tailwind theme values: radii, type styles and ease-slide. |
packages/ui/src/fonts.css | The font families: Space Grotesk, Space Mono and the Chinese fallbacks. |
An app that uses Tailwind imports @botiverse/raft-ui/styles.css. A page without Tailwind imports @botiverse/raft-ui/foundation.css and reads the custom properties: var(--layer-canvas).
How foundation.css is generated
packages/ui/scripts/build-tokens.mjs reads tokens.json and writes foundation.css. Never edit foundation.css by hand.
vp run @botiverse/raft-ui#build:tokens # write src/foundation.css
node packages/ui/scripts/build-tokens.mjs --check # exit 1 if it is out of date- Change a value in
tokens.json, runbuild:tokens, and commit both files. A test (foundation.generated.test.ts) fails whenfoundation.cssdoesn't matchtokens.json. - A token's path becomes its property name:
{layer.canvas}is--layer-canvas.$rootnames the group itself:{foreground.$root}is--foreground. A reference ("{palette.cyan.800}") becomesvar(--palette-cyan-800). - The format has no field for modes, so
tokens.jsonadds an extension,build.botiverse.raft:
| Field | Meaning |
|---|---|
modes.dark | The dark-mode value. A token without it has the same value in both modes |
scope: "root" | A value that doesn't change with the mode, such as the palette: declared once on :root |
scope: "every-mode" | An alias of a token that changes with the mode: declared again in each mode scope, so it resolves against that mode's value |
css: true | A value kept as a CSS expression that the format has no type for: color-mix(), a shadow with several layers |
static: true | A deprecated alias of a value that doesn't change with the mode |
The generated file has, in order:
- the palette, on
:where(:root); - light mode, on
:where(:root)and.light; - dark mode, on
.dark; - dark mode again under
@media (prefers-color-scheme: dark)for a root without.lightor.dark, so the page has the right colors before the app sets a class; - the aliases declared in every mode, then the deprecated names.
A .light element inside a dark page is a light island, for a preview or a portal; the dark: variant in styles.css follows the nearest mode class. See Dark mode.
Token grammar
Names are built from a few parts, so a name you haven't seen tells you what it is for.
| Kind | Pattern | Examples |
|---|---|---|
| Neutral roles | {role}(-{purpose})(-{intensity}) | layer-canvas, foreground-hint, line-seam, fill-pressed, foreground-on-hud |
| Color roles | {family}(-{slot}) | brand, brand-soft, danger-strong, info-muted |
| Ink steps | ink-{alpha} | ink-8, ink-40 |
| Palette | palette-{color}-{step} | palette-yellow-400, palette-lavender-800 |
| Statuses | task-status-{status}(-soft|-strong), presence-{state} | task-status-in-review-soft, presence-busy |
| Stickers | sticker-{part} | sticker-ink, sticker-edge, sticker-pressed |
| Shadows | elevation-{level}, focus-ring | elevation-float, elevation-sticker |
| Metrics | {part}-{property} | field-font-size, card-title-line-height |
Neutral roles. The role is one of four:
foreground-*: text and icons;layer-*: a surface, in the order things stack (frame, sidebar, canvas, card, popover);line-*: a line;fill-*: a fill laid into or over a surface.
The purpose says where the token is used: frame, sidebar, canvas, card, popover, code, hud, on-frame, on-hud, link, field. Intensity runs from the bare name down to -muted, then -hint for text, and from -hairline through -seam to -strong for lines.
Color roles. The family is one of brand, attention, info, success, warning, danger or inactive. The slot says which shade:
| Slot | Is |
|---|---|
bare (info) | The solid color: a sticker's fill |
-strong | A deep shade, readable as text |
-muted | A strong tint |
-soft | A pale tint, without a border; see Soft tints |
-foreground | Text on the solid color, where a family has its own |
-hover, -active | The pair a solid fill takes on hover and press. A family has both or neither |
-50 to -950 | The numbered ramp. Only brand and attention have one |
Opacity. Every layer token is opaque except layer-backdrop, the scrim. fill-muted and fill-strong are opaque. fill-pressed, fill-frame-pressed and sticker-pressed are translucent: they are overlays laid on top of a control (Pressed overlays).
Utilities. A Tailwind utility is the property's prefix and the token name: bg-layer-card, text-foreground-muted, border-line-strong. Shadows are named apart: shadow-float for elevation-float, shadow-sticker for elevation-sticker, shadow-focus for focus-ring. The numbered palette steps are only custom properties, not utilities; the bare bg-palette-yellow and its siblings are the exception.
Token groups
Each block below is generated from tokens.json and shows the light and dark values.
Palette
The raw colors the roles point at. The rules for using them directly are in Color.
| Token | Light | Dark |
|---|---|---|
| palette-yellow-50 | ||
| palette-yellow-100 | ||
| palette-yellow-200 | ||
| palette-yellow-300 | ||
| palette-yellow-400 | ||
| palette-yellow-500 | ||
| palette-yellow-600 | ||
| palette-yellow-700 | ||
| palette-yellow-800 | ||
| palette-yellow-900 | ||
| palette-yellow-950 | ||
| palette-lavender-50 | ||
| palette-lavender-100 | ||
| palette-lavender-200 | ||
| palette-lavender-300 | ||
| palette-lavender-400 | ||
| palette-lavender-500 | ||
| palette-lavender-600 | ||
| palette-lavender-700 | ||
| palette-lavender-800 | ||
| palette-lavender-900 | ||
| palette-lavender-950 | ||
| palette-pink-50 | ||
| palette-pink-100 | ||
| palette-pink-200 | ||
| palette-pink-300 | ||
| palette-pink-400 | ||
| palette-pink-500 | ||
| palette-pink-600 | ||
| palette-pink-700 | ||
| palette-pink-800 | ||
| palette-pink-900 | ||
| palette-pink-950 | ||
| palette-cyan-50 | ||
| palette-cyan-100 | ||
| palette-cyan-200 | ||
| palette-cyan-300 | ||
| palette-cyan-400 | ||
| palette-cyan-500 | ||
| palette-cyan-600 | ||
| palette-cyan-700 | ||
| palette-cyan-800 | ||
| palette-cyan-900 | ||
| palette-cyan-950 | ||
| palette-orange-50 | ||
| palette-orange-100 | ||
| palette-orange-200 | ||
| palette-orange-300 | ||
| palette-orange-400 | ||
| palette-orange-500 | ||
| palette-orange-600 | ||
| palette-orange-700 | ||
| palette-orange-800 | ||
| palette-orange-900 | ||
| palette-orange-950 | ||
| palette-lime-50 | ||
| palette-lime-100 | ||
| palette-lime-200 | ||
| palette-lime-300 | ||
| palette-lime-400 | ||
| palette-lime-500 | ||
| palette-lime-600 | ||
| palette-lime-700 | ||
| palette-lime-800 | ||
| palette-lime-900 | ||
| palette-lime-950 | ||
| palette-red-50 | ||
| palette-red-100 | ||
| palette-red-200 | ||
| palette-red-300 | ||
| palette-red-400 | ||
| palette-red-500 | ||
| palette-red-600 | ||
| palette-red-700 | ||
| palette-red-800 | ||
| palette-red-900 | ||
| palette-red-950 | ||
| palette-stone-50 | ||
| palette-stone-100 | ||
| palette-stone-200 | ||
| palette-stone-300 | ||
| palette-stone-400 | ||
| palette-stone-500 | ||
| palette-stone-600 | ||
| palette-stone-700 | ||
| palette-stone-800 | ||
| palette-stone-900 | ||
| palette-stone-950 | ||
| palette-cream-50 | ||
| palette-cream-100 | ||
| palette-cream-200 | ||
| palette-cream-300 | ||
| palette-cream-400 | ||
| palette-cream-500 | ||
| palette-cream-600 | ||
| palette-cream-700 | ||
| palette-cream-800 | ||
| palette-cream-900 | ||
| palette-cream-950 |
Text and icons
foreground and its tiers, and the text colors for the frame and the HUD. Usage and contrast: Color, Typography.
| Token | Light | Dark |
|---|---|---|
| foreground | ||
| foreground-muted | ||
| foreground-hint | ||
| foreground-disabled | ||
| foreground-inverse | ||
| foreground-on-hud | ||
| foreground-on-hud-mutedSecondary text on the HUD: the shortcut in a tooltip, the detail line of a toast. | ||
| foreground-on-frame | ||
| foreground-on-frame-muted | ||
| foreground-on-frame-active | ||
| foreground-link | ||
| foreground-active | ||
| foreground-hover |
Surfaces
Usage: Color.
| Token | Light | Dark |
|---|---|---|
| layer-canvas | ||
| layer-sidebar | ||
| layer-popover | ||
| layer-backdrop | ||
| layer-card | ||
| layer-hud | ||
| layer-frame | ||
| layer-frame-active | ||
| layer-code |
Fills
Usage: Color and Pressed overlays.
| Token | Light | Dark |
|---|---|---|
| fill-muted | ||
| fill-strong | ||
| fill-frame-pressed | ||
| fill-pressed |
Lines
Usage and widths: Color, Shape.
| Token | Light | Dark |
|---|---|---|
| line-strong | ||
| line-seam | ||
| line-hairline | ||
| line-frame-active | ||
| line-field | ||
| line-field-hover |
Ink steps
Translucent steps of ink that lines, secondary text and overlays are made from. Use the role tokens; reach for an ink step only for a one-off wash.
| Token | Light | Dark |
|---|---|---|
| ink-2 | ||
| ink-4 | ||
| ink-6 | ||
| ink-8 | ||
| ink-10 | ||
| ink-16 | ||
| ink-20 | ||
| ink-30 | ||
| ink-40 | ||
| ink |
Color roles
What each color means: Meanings of color.
| Token | Light | Dark |
|---|---|---|
| brand-50 | ||
| brand-100 | ||
| brand-200 | ||
| brand-300 | ||
| brand-400 | ||
| brand-500 | ||
| brand-600 | ||
| brand-700 | ||
| brand-800 | ||
| brand-900 | ||
| brand-950 | ||
| brand-strong | ||
| brand-soft | ||
| brand-edge | ||
| brand-glow | ||
| brand-hoverPressing a sticker lays sticker-pressed (12% ink) over its colour. | ||
| brand-active |
| Token | Light | Dark |
|---|---|---|
| attention-50 | ||
| attention-100 | ||
| attention-200 | ||
| attention-300 | ||
| attention-400 | ||
| attention-500 | ||
| attention-600 | ||
| attention-700 | ||
| attention-800 | ||
| attention-900 | ||
| attention-950 | ||
| attention-strong | ||
| attention-soft | ||
| attention-hover | ||
| attention-active |
| Token | Light | Dark |
|---|---|---|
| infoEvery solid is a sticker: ink type on the colour in both modes. | ||
| info-strong | ||
| info-muted | ||
| info-soft | ||
| info-hover | ||
| info-active |
| Token | Light | Dark |
|---|---|---|
| success | ||
| success-foreground | ||
| success-strong | ||
| success-muted | ||
| success-soft |
| Token | Light | Dark |
|---|---|---|
| warning | ||
| warning-foreground | ||
| warning-strong | ||
| warning-muted | ||
| warning-soft | ||
| warning-hover | ||
| warning-active |
| Token | Light | Dark |
|---|---|---|
| dangerdanger = the destructive sticker (red fill, ink type); danger-strong is the error ink for invalid fields and destructive menu items. | ||
| danger-foreground | ||
| danger-strong | ||
| danger-muted | ||
| danger-soft | ||
| danger-hover | ||
| danger-active |
| Token | Light | Dark |
|---|---|---|
| inactive | ||
| inactive-foreground | ||
| inactive-soft |
Statuses
Task statuses and presence lights: Statuses.
| Token | Light | Dark |
|---|---|---|
| task-status-todo | ||
| task-status-todo-soft | ||
| task-status-todo-strong | ||
| task-status-in-progress | ||
| task-status-in-progress-soft | ||
| task-status-in-progress-strong | ||
| task-status-in-review | ||
| task-status-in-review-soft | ||
| task-status-in-review-strong | ||
| task-status-done | ||
| task-status-done-soft | ||
| task-status-done-strong | ||
| task-status-closed | ||
| task-status-closed-soft | ||
| task-status-closed-strong |
| Token | Light | Dark |
|---|---|---|
| presence-online | ||
| presence-busy | ||
| presence-error | ||
| presence-offline | ||
| presence-external |
Stickers
Usage: Stickers.
| Token | Light | Dark |
|---|---|---|
| sticker-ink | ||
| sticker-paper | ||
| sticker-edge | ||
| sticker-pressed |
Shadows and the focus ring
Usage: Elevation, Accessibility.
| Token | Light | Dark |
|---|---|---|
| elevation-floatControls never cast a shadow; only overlays float. | ||
| elevation-sticker |
| Token | Light | Dark |
|---|---|---|
| focus-ring |
Metrics
The type metrics a field and a card title share with the parts laid out beside them. Usage: Typography.
| Token | Light | Dark |
|---|---|---|
| field-font-size | ||
| field-font-weight | ||
| field-line-height |
| Token | Light | Dark |
|---|---|---|
| card-title-font-size | ||
| card-title-font-weight | ||
| card-title-line-height |
Tokens defined in styles.css
These are Tailwind theme values in styles.css, not entries in tokens.json, so they have no generated table.
| Tokens | Owner |
|---|---|
radius-sticker, radius-chip, radius-control, radius-container, radius-dialog | Shape |
text-hero to text-label, text-mono-meta, text-mono-code | Typography |
ease-slide | Motion |
font-sans, font-heading, font-mono | Typography |
Deprecated names
Earlier names are kept as aliases until raft-ui 1.0, in the deprecated group of tokens.json. Each one points at the token to use instead, so old code keeps working while it moves.
| Token | Light | Dark |
|---|---|---|
| layer-panel | ||
| layer-inset | ||
| layer-canvas-muted | ||
| layer-hud-foreground | ||
| foreground-strong | ||
| foreground-icon | ||
| foreground-placeholder | ||
| line | ||
| line-muted | ||
| primary-50 | ||
| primary-100 | ||
| primary-200 | ||
| primary-300 | ||
| primary-400 | ||
| primary-500 | ||
| primary-600 | ||
| primary-700 | ||
| primary-800 | ||
| primary-900 | ||
| primary-950 | ||
| primary-strong | ||
| primary-soft | ||
| primary-edge | ||
| primary-glow | ||
| primary-hover | ||
| primary-active | ||
| accent-50 | ||
| accent-100 | ||
| accent-200 | ||
| accent-300 | ||
| accent-400 | ||
| accent-500 | ||
| accent-600 | ||
| accent-700 | ||
| accent-800 | ||
| accent-900 | ||
| accent-950 | ||
| accent-strong | ||
| accent-soft | ||
| accent-hover | ||
| accent-active | ||
| theme-shadow-xs | ||
| theme-shadow-sm | ||
| theme-shadow-md | ||
| theme-shadow-lg | ||
| theme-shadow-xl | ||
| theme-shadow-float | ||
| theme-shadow-sticker | ||
| theme-focus-ring | ||
| color-brutal-yellow-50 | ||
| color-brutal-yellow-100 | ||
| color-brutal-yellow-200 | ||
| color-brutal-yellow-300 | ||
| color-brutal-yellow-400 | ||
| color-brutal-yellow-500 | ||
| color-brutal-yellow-600 | ||
| color-brutal-yellow-700 | ||
| color-brutal-yellow-800 | ||
| color-brutal-yellow-900 | ||
| color-brutal-yellow-950 | ||
| color-brutal-purple-50 | ||
| color-brutal-purple-100 | ||
| color-brutal-purple-200 | ||
| color-brutal-purple-300 | ||
| color-brutal-purple-400 | ||
| color-brutal-purple-500 | ||
| color-brutal-purple-600 | ||
| color-brutal-purple-700 | ||
| color-brutal-purple-800 | ||
| color-brutal-purple-900 | ||
| color-brutal-purple-950 | ||
| color-brutal-pink-50 | ||
| color-brutal-pink-100 | ||
| color-brutal-pink-200 | ||
| color-brutal-pink-300 | ||
| color-brutal-pink-400 | ||
| color-brutal-pink-500 | ||
| color-brutal-pink-600 | ||
| color-brutal-pink-700 | ||
| color-brutal-pink-800 | ||
| color-brutal-pink-900 | ||
| color-brutal-pink-950 | ||
| color-brutal-cyan-50 | ||
| color-brutal-cyan-100 | ||
| color-brutal-cyan-200 | ||
| color-brutal-cyan-300 | ||
| color-brutal-cyan-400 | ||
| color-brutal-cyan-500 | ||
| color-brutal-cyan-600 | ||
| color-brutal-cyan-700 | ||
| color-brutal-cyan-800 | ||
| color-brutal-cyan-900 | ||
| color-brutal-cyan-950 | ||
| color-brutal-orange-50 | ||
| color-brutal-orange-100 | ||
| color-brutal-orange-200 | ||
| color-brutal-orange-300 | ||
| color-brutal-orange-400 | ||
| color-brutal-orange-500 | ||
| color-brutal-orange-600 | ||
| color-brutal-orange-700 | ||
| color-brutal-orange-800 | ||
| color-brutal-orange-900 | ||
| color-brutal-orange-950 | ||
| color-brutal-lime-50 | ||
| color-brutal-lime-100 | ||
| color-brutal-lime-200 | ||
| color-brutal-lime-300 | ||
| color-brutal-lime-400 | ||
| color-brutal-lime-500 | ||
| color-brutal-lime-600 | ||
| color-brutal-lime-700 | ||
| color-brutal-lime-800 | ||
| color-brutal-lime-900 | ||
| color-brutal-lime-950 | ||
| color-brutal-red-50 | ||
| color-brutal-red-100 | ||
| color-brutal-red-200 | ||
| color-brutal-red-300 | ||
| color-brutal-red-400 | ||
| color-brutal-red-500 | ||
| color-brutal-red-600 | ||
| color-brutal-red-700 | ||
| color-brutal-red-800 | ||
| color-brutal-red-900 | ||
| color-brutal-red-950 | ||
| color-brutal-stone-50 | ||
| color-brutal-stone-100 | ||
| color-brutal-stone-200 | ||
| color-brutal-stone-300 | ||
| color-brutal-stone-400 | ||
| color-brutal-stone-500 | ||
| color-brutal-stone-600 | ||
| color-brutal-stone-700 | ||
| color-brutal-stone-800 | ||
| color-brutal-stone-900 | ||
| color-brutal-stone-950 | ||
| color-brutal-cream-50 | ||
| color-brutal-cream-100 | ||
| color-brutal-cream-200 | ||
| color-brutal-cream-300 | ||
| color-brutal-cream-400 | ||
| color-brutal-cream-500 | ||
| color-brutal-cream-600 | ||
| color-brutal-cream-700 | ||
| color-brutal-cream-800 | ||
| color-brutal-cream-900 | ||
| color-brutal-cream-950 | ||
| secondary-50 | ||
| secondary-100 | ||
| secondary-200 | ||
| secondary-300 | ||
| secondary-400 | ||
| secondary-500 | ||
| secondary-600 | ||
| secondary-700 | ||
| secondary-800 | ||
| secondary-900 | ||
| secondary-950 |
The renames follow a few patterns:
primary-* -> brand-*
accent-* -> attention-*
color-brutal-{color}-{n} -> palette-{color}-{n} (purple -> lavender)
secondary-{n} -> palette-stone-{n}
layer-panel -> layer-canvas
layer-canvas-muted -> layer-sidebar
layer-inset -> fill-muted
layer-hud-foreground -> foreground-on-hud
foreground-strong -> foreground
foreground-icon -> foreground-muted
foreground-placeholder -> foreground-hint
line -> line-seam
line-muted -> line-hairline
theme-shadow-float/lg/xl -> elevation-float (utility: shadow-float)
theme-shadow-sticker -> elevation-sticker (utility: shadow-sticker)
theme-focus-ring -> focus-ring (utility: shadow-focus)
theme-shadow-xs/sm/md -> none: controls cast no shadow, drop the classThe codemod
raft-ui ships a codemod that rewrites the old names in Tailwind utilities (bg-…, hover:border-…/50) and in CSS custom properties (var(--…)):
node node_modules/@botiverse/raft-ui/codemods/token-names.mjs <dir-or-file>... [--dry]- It rewrites
.ts,.tsx,.js,.jsx,.mjs,.cjs,.cssand.htmlfiles, and skipsnode_modules,distandbuild.--drylists the files it would change. - It renames references and leaves declarations alone, so running it twice is safe.
- Review the result by hand. A guard that forbade an old name (a lint rule, a test, an allowlist) now forbids the new one, and a pattern built from parts, such as a regular expression over class names, keeps the old name.
- The docs never use a deprecated name:
scripts/check-docs.mjsfails a page that does.
Changing a token
- Add or change a value only in
tokens.json, then regeneratefoundation.cssin the same change. - Give a new token a name that follows the grammar above, and add its utility to
styles.cssif components need one. - Rename a token by keeping the old name in the
deprecatedgroup, pointing at the new one, and adding the rename to the codemod. Remove aliases only in a major version. - When a text, surface or tint value changes, recheck the contrast guarantees in Color for both modes.
- Update the foundation page that owns the token in the same pull request (Authoring).
Related
- Color: what each color token is for, and the contrast it guarantees.
- Typography: the type styles and the metric tokens.
- Elevation: shadows and the focus ring.
- Dark mode: what changes between the modes.