Skip to content

The Graphite+ identity

The design language behind the kin interface: a cool, near-monochrome graphite ground with colour held in reserve for signal. This page explains the two-channel colour split and the palette behind it.

Cyan voice, periwinkle thinking

One idea organizes the whole interface: kin's live voice is cyan, its thinking is periwinkle.

Streamed output and the assistant speak in a clean cyan — the live signal. The reasoning trace is a distinct, recessive periwinkle — the private thinking channel. They are two cool hues, deliberately close to the graphite ground but clearly apart from each other, so you can tell at a glance whether you are reading kin's answer or watching it reason. The split is the point: a single colour for "kin is talking to you" and a quieter colour for "kin is working it out."

The same two roles carry through everything — including fenced code blocks, where cyan marks keywords and periwinkle marks class and type names, so code reads as part of kin rather than as a foreign syntax theme.

The palette

Colours are named by role, not by hue, so a re-tint is a one-place edit. The role names below are the constants defined in theme.py.

Role Hue Hex Used for
GROUND graphite #101114 Screen, transcript, collapsibles — one flat surface
FG bone #E3E3E6 Body text
PRIMARY cyan #5FBFD6 The live signal: kin speaking, the spinner, accents, the wordmark
REASON periwinkle #9E97D2 The private reasoning / thinking channel
SUCCESS green #5FC198 A tool succeeded
WARNING amber #D6A855 Warnings — distinct enough from cyan that the two never read alike
ERROR coral #E5786E Errors and failures

These constants are the single place the palette is named. The Textual theme wires them into the UI's CSS tokens, and the transcript widgets import the same constants directly for Rich text styles (Rich cannot read a CSS variable) — so a styled CSS rule and a hand-built renderable always land on the same colour.

Contrast on graphite

The recessive greys and the shimmer base are tuned for WCAG against GROUND (#101114), not chosen by eye — the ratios are measured with the standard relative-luminance formula and live in theme.py next to each constant. Body-text grey (GREY_LO, reasoning tails / diff metadata / pygments comments) clears AA body at 4.65:1; the shimmer base (PRIMARY_DIM, the wordmark hairline + cosine-sweep rest colour) clears AA Large at 3.21:1 while keeping a ~5.7-point delta below PRIMARY so the sweep stays visible.

Glyph vocabulary

Alongside the palette, theme.py owns the functional glyph vocabulary — the full set of disclosure, connector, and chrome glyphs (G_RUNNING, G_CARET_*, G_ELBOW, G_BULLET, G_OK, G_ERR, G_AGENT, G_RETRY, G_PAUSE, G_TODO_*, METER_*, SPINNER_STYLES, …). These are functional — a status, a connector, a spinner frame — not skin, so they're shared semantics with kin's Go TUI and they live ONLY in theme.py. A literal glyph anywhere else in kin.tui is an audit-fail: a future re-tint of G_BULLET would otherwise leave stranded literals off-identity. Widgets and renderers import these constants; the same invariant covers palette colours and glyph vocabulary.

Spinner vibes

The busy spinner is choosable by vibe — a small curated set of named styles in theme.py's SPINNER_STYLES, each a SpinnerStyle (constant cell-width frames + cadence + a one-word descriptor). Pick one interactively with /spinner or set the spinner key / KIN_SPINNER env directly; resolve_spinner falls back to the default on unknown names and forces the ASCII-safe line style under a truthy KIN_ASCII.

name vibe frames cadence
bloom (default) breathing · ✢ ✳ ✶ ✻ ✽ ✻ ✶ ✳ ✢ 0.14s
braille classic ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ 0.10s
arc orbital ◜ ◠ ◝ ◞ ◡ ◟ 0.12s
toggle metronome ⊶ ⊷ 0.35s
line plain - \ | / 0.13s

Exactly one frame-stepped spinner position exists in the chrome — the status bar's busy glyph — plus its one transcript mirror, a running workflow card; every other live surface expresses liveness by colour only (the shimmer and pulse). That restraint is the "one heartbeat, colour elsewhere" invariant (POLISH r11) in REFERENCE.md.

Why calm

The restraint is deliberate. kin reads as a precision tool, not a dashboard: a near-monochrome graphite field where colour means something every time it appears. The interface is flat and unified — one ground shade for the screen, the transcript, and collapsibles, with a single clearly-lifted shade reserved for overlays like modals. Nothing competes for attention until it carries signal. That is why the live voice and the thinking channel get the only two saturated cool hues, and why status colours (green, amber, coral) stay reserved for the moments they actually report. Colour is information here, so it is spent sparingly.