gpui-kit GitHub

Token model

Authority

The documents under crates/gpui-kit-tokens/tokens/ are the authority, and they sit inside the crate that embeds them so that crate can be packaged on its own. gpui-kit-tokens embeds and validates every bundled theme; gpui-kit-theme is the only GPUI adapter. studio-dark is the default and studio-light is its light counterpart.

JSON token document
    ↓
typed semantic roles
    ↓
GPUI Theme global
    ↓
component recipes
    ↓
application views

No crate reads a path outside this repository.

Three layers

Raw values

Literal values live only in the token document, in one palette per theme:

"palette": { "neutral": { "200": "#1b1b1b" } }

Everything else references the palette, optionally with a hexadecimal alpha suffix, so a theme is retuned by editing its scales rather than every role:

"raised": "{neutral.200}",
"hover": "{neutral.850}/24"

Semantic roles

Callers select meaning:

tokens.surface(Surface::Raised)
tokens.semantic(SemanticColor::Danger)
tokens.radius(Radius::Dialog)

Component usage

Components combine semantic roles:

popover.background = surface.overlay
popover.border = interactive.hairlineStrong
popover.radius = radius.card

Component aliases are implemented in Rust while the catalog is small. Move an alias into JSON only when several components must share and evolve it together.

Color

Colors use #RRGGBB, #RRGGBBAA, or a {group.step} palette reference. Alpha is part of a token where it expresses a reusable wash, hairline, or effect. Application views do not invent new palette colors.

Large planes use surface roles. Accent and semantic colors remain compact.

Contrast

cargo run -p xtask -- tokens check fails when a theme drops below its contrast floor: 4.5:1 for body text and text.onAccent over semantic.accent, 3.0:1 for text.faint and for status colors, which never carry required instructions on their own. semantic.accentStrong is an emphasis, border and hover color rather than a text-bearing fill, so it is held to the non-text minimum.

Elevation, layers, and density

elevation describes the shadow each surface casts, zIndex fixes the paint order of floating surfaces, and density scales spacing, control geometry and type independently. Density is applied when a Theme is built, and gpui_kit::set_density rebuilds the active theme and repaints every window. Colors and radii never change with density.

Themes at runtime

ThemeRegistry holds every registered document. An application registers its own JSON with ThemeRegistry::register_json, replacing a bundled theme when it reuses its id, and switches with gpui_kit::activate_theme.

Typography

The theme provides Geist and Geist Mono with platform fallbacks. Size, line-height, and weight travel as one TypeStep; consumers should not mix a size from one step with line-height from another.

Static Medium, SemiBold, and Bold font files are included because not every GPUI text backend applies a variable font's weight axis.

Motion and effects

Motion tokens store duration and cubic-bezier control points. Component motion evaluates CSS-compatible curves through the pure CubicBezier implementation.

Overlay surfaces are opaque on every platform; GPUI has no per-element backdrop blur, and gpui-kit does not fake one.

The two rings live here too. effect.selectedRingAlpha draws the inset ring on the current answer; effect.focusRingWidth and effect.focusRingAlpha draw the outset ring, in color.interactive.focus, on whatever holds the keyboard. They are separate tokens because they answer separate questions, and a theme that collapsed them would leave a reader unable to tell selection from focus.

Validation

TokenDocument::validate rejects:

  • invalid RGB/RGBA literals and unresolvable palette references;
  • empty metadata;
  • non-increasing spacing and control heights;
  • invalid type size, line-height, or weight;
  • effect and opacity alpha outside 0–1, or a non-positive focus ring width;
  • negative elevation blur;
  • z-index layers that are not strictly increasing;
  • density factors outside 0.5–1.5, or a comfortable axis that is not 1.

Run:

cargo run -p xtask -- tokens generate
cargo run -p xtask -- tokens check