Voice Studio · the design system, rendered · 2026-07-29

Everything that
exists, and its
one job.

Open this before building a screen. Every token below is shown at the size and on the ground it is actually used on, and the law that governs it is printed beside it — not collected in a list at the end, because the moment someone is about to break a rule is the moment they are looking at the thing.

The ruling ledger is DESIGN.md; it wins any disagreement with this page. Read it →

Colour says one of two things

Which thing this is

Five identity pastels. Each holds one job across the entire product, forever. That fixed assignment is what lets forty screens be derived from eight.

And never both at once

How you did

Four verdict states, three of them colours. They stand on paper or white, never on a pastel, and they are never borrowed for a rating, a category, a chart or a tab.

Identity pastels — which

A pastel is never chosen because a card needed a colour. If four study-set cards are four different pastels for variety, the system is already broken — categories are told apart by icon and text.

peach #F6D9B2

Goals and the path — the journey. Goal cards, goal nodes, the star.

rose #F2BDC3

Speaking — your voice, the hard modality. The coaching panel in every speaking state.

lilac #CECBF7

Conversation and people. Mei’s scene, the partner turns, anything with a human in it.

sky #D7E8F6

Listening. Listen-and-choose exercises, model playback regions.

mint #BCEBD9

The library — study sets, atoms, saved things. Also the world outside the phone frame.

NeverA verdict. A pastel says which, never how you did — and no verdict colour may ever stand on top of one.
NeverA button. Black is the action colour, and the only colour a control turns when it becomes active.
At mostTwo dominant pastel regions in one viewport. A third turns identity into decoration.

Verdicts — how you did. Four states, not three

The fourth is the expensive one to forget. Ink is not “correct” — ink is “we did not score this.” The learner skipped the syllable, the mic missed it, or the model returned nothing. A cleared part rendering the same as an unsaid one is a false green, and an invisible one.

CLEAR #25845F · #D9F0E6

The judged part landed. Outside the analyzer it is also aggregate success — goal complete, take stored, progress fill.

CLOSE #E8368F · #FDE2EF

Recognisable, a repair remains. Magenta, not amber — amber went brown on warm paper and collapses into red under red-green colour blindness, on the one distinction our trust rests on.

FIX #D44749 · #FDE4E4

A different sound or contour came out. Never used for an operational failure — that is what alert is for.

not scored ink #111015 · locked #F1EFE9

No verdict at all. Ink type, and the bar left on the empty locked track — the absence has to be visible as an absence, which any grade colour would hide.

AlwaysOn paper or white. Green on rose vibrates, red loses its alarm inside its own colour family, and the good result becomes the least readable thing on screen.
AlwaysWith a word, or another non-colour channel. Colour is never the only carrier of a verdict.
NeverA rating, a category, a chart fill or a tab. A ★4.9 is ink — painting a good rating in the “needs work” colour is the exact error this rule stops.

Operational alert

alert #B4261E

Something in the room or the wire went wrong. Tool surfaces and system failure only — never on a learner’s score.

Tools have to be able to say capture failed, stop the session. Reaching for FIX red to say it is the most natural mistake in the system and the most damaging: a verdict colour appearing outside a verdict destroys the one rule that makes the analyzer readable. So the alert is a deeper, more desaturated red — unmistakably not the analyzer’s.

Set side by side the difference is obvious, and it has to survive being seen one at a time, months apart, in a bright classroom.

FIX #D44749a verdict on a sound
alert #B4261Ea take that did not land

Neutrals — the ground everything else stands on

paper#FBF8F3The learner page ground.
panel#F0ECE4Structural regions — and the tool ground.
locked#F1EFE9The empty track: not earned, not scored, not yet landed.
ink#111015Type, and every active action.
muted#6F6A68Eyebrows and metadata. Not for body text on colour.
lineink @ 10%The one hairline. Cards and rows use it, nothing heavier.

Type, at the size it actually ships

Every row below is set at its real size. 12 is the floor, and it does not move — not for a tool, not for a dense row, not to save a scroll. Pinyin and Chinese are learning content, not metadata: they never shrink to preserve a no-scroll layout; the screen grows instead. Hanken Grotesk for Latin and pinyin, Noto Sans SC for Chinese.

Say it out loud, from day one.Display / onboarding · 28–34 · 500–600
Introduce yourself naturallyRoot page title · 26–30 · 500–600
Speak your answerLesson title · 18–22 · 600
One change at a timeSection heading · 18–20 · 600–700
You said s. In 是, sh needs the tongue slightly behind the ridge.Standard body · 15–16 · 500–600
Model plays the sentence and never uses one of your daily attempts.Supporting body · 14 · 500–600
All 13 parts scored · 2 to repairMetadata / caption · 12 floor · 600–700
Speak nowButton label · 14–15 · 700
wǒ shì Yuènán rénPinyin in a scored target · 15–16 · 700
我是越南人。Hanzi in an analyzer · 28–34 · 600
中国Hanzi in an answer choice · 20–24 · 600
Say it · not scored yet.eyebrow · 12 · 700 · .11em
NeverSmall, grey and heavy together. It is the hardest legibility recipe there is. Below 14, drop to weight 600 and use ink at reduced opacity rather than muted on colour.
NeverWeight 900. Hierarchy comes from proportion and moderate weight; 900 is the voice of the retired direction.

Shape and spacing

Three surface levels and nothing else: flat pastel panels for structural regions, white cards with a hairline and one subtle low blur for contained content, black fills for the current action. Expressive, rotated geometry is allowed in exactly two places — onboarding’s tiles and the home goal card. Analyzer panels, tone graphs and dense controls stay regular, always.

15–18Controls and small cards. .row, .button, .notice.
20–22Standard panels and cards. .panel, .card.
full pillFilters and compact status. .pill, .stat.
circleIcon and voice controls. .icon-btn.

The spacing scale — 4 · 8 · 12 · 16 · 18 · 24 · 32

4 8 12 16 18 24 32

Mobile gutter is 18 on a learner screen and 14 on a tool screen — that tightening, plus rows in place of cards, is the tool register. There is no colour the tools own.

Primitives, shown as themselves

These live in src/app.css. A thing earns a place there by appearing on three or more screens; everything else is Tailwind utilities in the markup, where you can see it while you read the screen.

.card

White, one hairline, one low blur. Contained content — the thing you are looking at. This block is one.

.panel

A flat structural region, no shadow. Takes a pastel when it is identifying an activity; stays panel when it is just ground.

.progress

Ink by default. It takes an activity tint inside that activity’s lesson — never a verdict colour, because a lesson position is not a score.

.pill — and its variants
Recommended HSK CLEAR CLOSE FIX

.on is black, because black is what a control turns when it is active. The three verdict pills are the analyzer’s vocabulary and are only ever used to report a verdict — a track chip, a category or a count never wears one.

.button — and its variants

Minimum height 52, so it clears the 44 touch floor with room. .mint is the one pastel button and it is library-only — adding a set to your shelf. Everywhere else, the primary action is black.

.icon-btn

39px drawn, in a header row that carries the rest of the 44. Back, help, play.

The tool register

Teacher, rater and admin. Same tokens, same components, tighter spacing, a higher information rate — an operator reads their screen fast, in a bright room, with a student waiting. This section sits on panel because that is the tool ground, and the ground plus a header that states the mode in words is the whole mark. The retired slate-blue staff accent is gone: a sixth colour with one job is a sixth thing to keep straight, and a roster of student names was never mistakable for a learner screen.

.row — the tools’ unit of content, where the learner screens use a card
07我不知道。 1.57s · 290/440ms · 9b3e6a Saved
28这个多少钱? 2.05s · 290/370ms · no receipt yet Pending
.notice — plain · .warn · .alert
A neutral operational statement. White, hairline, no colour claimed.
Completion is refused — two takes are still on the phone. The guard working is not a failure, so it sits on locked.
Capture failed — stop the session. This is alert, the one place a red appears outside a verdict.
.field — label above, control below

Never a placeholder doing a label’s job: a placeholder disappears exactly when the teacher looks up to check what they typed.

.stat — the capture receipt, and the only colour a tool spends
Saved Pending Failed

Saved is green and it is not an exception: green already carries aggregate success outside the analyzer, and a durability receipt is exactly that — the bytes landed. It says nothing about the speech.

Pending is ink on locked. A take on the phone has not landed, and no screen may say “saved” over audio that is still on a device.

Failed is alert, never FIX red — and it means the bytes did not land, never that the speaking was bad. Capture is not scoring; errors are data and are never marked.

There is no fourth receipt and no discarded state. A bad take’s only escape hatch is a re-record: aborted and false-start takes are corpus data, and no operator can throw one away.

The grade mechanic — data-grade and data-roll

A syllable is scored as three independent parts — initial · final · tone — and the character reports the worst of them. Rather than scatter that across a dozen classes, data-grade on any element tints its pinyin and its drawn diacritic together, and data-roll on the syllable drives the grade bar, the verdict word and the character. One attribute, so a screen cannot disagree with itself.

All four states, live
wo Clear
nan Close
shi Fix
yue Not scored

good. The bar and the word go green; the character stays ink.

close. Only the tone mark is magenta — the initial and final were fine.

fix. The initial is red, the character follows the worst part.

unjudged. Ink type, a broken mark, and the bar left on the empty track.

This asymmetry is the whole design. Colouring eleven good syllables green to mark two problems buries the two problems — but making CLEAR ink is worse, because ink already means “we did not score this”, and the product would be quietly claiming credit for syllables the mic never heard. So green stays and the emphasis is bought elsewhere: only a part with work left in it colours its character, and the eye lands on the two problems because they are the only characters wearing colour.

data-all-clear — every part judged, and clear 100% · all 13 parts scored
wo Clear
shi Clear
yue Clear
nan Clear
ren Clear

The characters join the green in exactly one situation: every part judged and clear. That is not a verdict on a component, it is the aggregate over all of them — so a perfect take is the one time the whole target lights up, and it earns the moment. A take with a dropped syllable has not earned it and is excluded by the mechanic itself.

Rendered from src/app.css — nothing on this page is a redrawn copy, so it cannot drift from what the screens actually get. If a token here looks wrong, the fix is in that file and this page follows. Rulings and their reasons live in DESIGN.md.