Skip to content

Writing rules

Sentence case in two languages, never ALL CAPS, labels that start with a verb, numbers with a true minus — and the words the system uses for itself.

Do

Example Why
Create campaign sentence case: the first letter, then only names
Save changes a button says what happens
Where the money goes a heading is a sentence, not a title

Don’t

Example Why
CREATE CAMPAIGN shouting — the deck tool re-cases a caps heading at parse time
Create Campaign Title Case — every word capitalised reads as a name
WHERE THE MONEY GOES no ALL CAPS, anywhere
Where The Money Goes Title Case on a heading

Keep caps

Example Why
KYC an acronym
DSP an acronym
TL;DR an abbreviation with punctuation
B2B a digit token
Q3 a digit token
MAFO a house product — brand-style name
iRTB a product’s own casing, kept as typed

Caps by component

Example Why
About us the Heading pair’s web eyebrow — typed ‘About us’, the component sets UPPER and +6% tracking
Workspace the rail’s Nav section — typed ‘Workspace’, the component sets UPPER and +8%

Only these two show caps, and only because their component sets textCase — the text underneath stays sentence case, searchable and re-usable. Never type caps into a label. Unknown tokens keep their caps when re-cased: the brand-safe default — so a heading typed in caps with a brand in it comes out right.

Do

Example Why
Export report verb + object: the outcome
Add member two words carry it
Delete campaign the danger button names what it destroys
Try again an error offers the next step

Don’t

Example Why
Click here to export the report the sentence belongs in the description, not the button
OK says nothing about what happens (Alert dialog: name the action)
Submit a form word, not an outcome
Yes a yes/no pair forces the reader back to the question

Do

Example Why
−12.5% the true minus U+2212 — Poppins has the glyph; a hyphen is shorter and sits lower
1,240 thousands grouped with a comma
$1.2K one style per chart (Auto · 1,000 · 1,000.00 · 1.2K); the unit is a verbatim prefix or suffix, never a conversion

Don’t

Example Why
-12.5% a hyphen-minus
1240 no grouping past three digits
$1.2K and $900 two styles in one chart — 1.2K beside 900 reads as two units

Vocabulary — how the system names itself

Section titled “Vocabulary — how the system names itself”
Term The rule
Component names Sentence case, the style after a spaced hyphen: Button - secondary, Icon button - ghost, Alert dialog, Data table. One name in Figma, the registry and the docs.
Properties and values Property=value, the property Capitalised and the value lowercase: Size=small, State=hover, Tone=neutral, Style=connected, Orientation=vertical.
States default · hover · focus · disabled, plus the state a component’s job needs — pressed on Button, on on Toggle, error on Input; a Switch has four and a Nav link two; selected where a thing is chosen (Table row, Nav item active).
Sizes small · medium · large. Controls stand 28 / 36 / 44 tall — a scale that has no name yet.
Tones blue · red · yellow · neutral on the KPI tile; the same with green and dark on Badge and Chip; accent · success · danger · neutral on the Icon list; neutral · info · success · warning · danger on Alert.
The contract Every description ends the same way: Use when · Do not use when · Relates to · Anti-patterns · Keywords — one text in Figma and in the registry item’s meta.
Icons Lucide names as Lucide spells them — chevron-down, circle-check, layout-dashboard — as Icon/<name>; a custom icon needs a documented Lucide gap.
Marks A logo is an asset placed from assets/logo or Brand/ — Logo/Pepper mark, Logo/MAFO — never re-drawn, never an emoji, a product’s mark where that product is the subject — its rail, its page on the site — and the corporate mark never inside a product.
Tokens Slash paths in Figma, var(--pui-…) on the web, the same words in both: surface/card is --pui-surface-card. Never a hex in a component, never a value the source lacks — ask, then add it to tokens/*.json.

Generated content — marked where it appears

Section titled “Generated content — marked where it appears”

Do

Example Why
“Q3 revenue rose 12 % on iRTB volume; MAFO margins held at 41 %.” the answer carries Echo’s own avatar — the brand’s mark, which is what says a machine wrote it — and the feedback row; the mark is said once, so no name and no second glyph sit beside it, and the reader knows before reading
Ask Echo a control that invokes the model carries the sparkles glyph — the one glyph reserved for AI

Don’t

Example Why
“Q3 revenue rose 12 % on iRTB volume; MAFO margins held at 41 %.” the same answer as a bare bubble — a machine’s text passing as a person’s; a Bubble is never used outside Message
✨ Ask Echo an emoji as the mark — an emoji is never a glyph (Icons & logos); the sparkles Lucide glyph is the mark

The rule: text a model generated is labelled ‘AI’ where it appears — Echo’s own avatar in a chat, which carries the brand’s mark and is said once, a neutral ‘AI’ Badge on a Card or an Insight the model wrote — and a control that sends something to the model carries the sparkles glyph; nothing else uses that glyph. The label is neutral, never a colour team: the mark states provenance, not status. Feedback controls (copy · helpful · not helpful) sit under every generated answer; the mark and the feedback are what a design system can do.

Each section is a Do row and a Don’t row of real components, the reason under each specimen. The mark and the sparkles glyph are the Chat page’s (Message, Composer).

The same page in the Figma library: Writing rules.

Pepper UI v1.5.0

The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.