Content design — UX writing
foundations specs/foundations/ux-writing.kmd
Voice, tone, vocabulary, and pattern guide for every user-facing string in Koder products. Material parity (`/foundations/content-design/style-guide/ux-writing-best-practices`). Extends `errors/user-facing-messages.kmd` (which covers error copy only) to all UI text — buttons, labels, hints, empty states, success messages.
When this spec applies
Primary triggers
- Write any UI text in a Koder product
All triggers
- Write or review any user-facing string (button, label, hint, etc.)
- Localize a string to pt-BR or en-US
- Author a docs/blog post for koder.dev
Specification body
Spec — Content design / UX writing
Facet Visual do Koder Design. Material parity: https://m3.material.io/foundations/content-design/style-guide/ux-writing-best-practices.
Voice
Koder products speak as:
- Knowledgeable but not condescending — explain WHAT and (when helpful) WHY, never assume the user is wrong
- Direct, not bossy — "Save" not "You should save now"
- Warm, not sappy — friendly without faux-cheer ("Yay!" is out)
- Specific — "Saved at 14:32" beats "Saved successfully"
- Confident — declarative, present tense
Tone variants
Tone shifts by context but voice stays constant.
| Context | Tone | Example |
|---|---|---|
| Success | Brief + specific | "Profile updated." |
| Error | Helpful + non-blaming | "We couldn't reach the server. Check your connection and try again." |
| Empty state | Inviting | "No projects yet — create your first one to get started." |
| Onboarding | Encouraging + brief | "Welcome to Koder Talk. Let's pick a wake word." |
| Destructive confirm | Serious + clear | "Delete 12 messages? This can't be undone." |
| Loading | Quiet | "Loading…" (avoid blank fluff like "Please wait while we…") |
R1 — Lengths
| Element | Max chars (en-US baseline) | Notes |
|---|---|---|
| Button label | 24 | Verb-first ("Save changes", "Delete") |
| Tab label | 16 | Noun ("Profile", "Activity") |
| Field label | 32 | Noun phrase ("Display name") |
| Hint text | 80 | Helpful one-liner |
| Error message | 140 | Brief + actionable (errors/user-facing-messages.kmd) |
| Section title | 40 | Noun phrase |
| Tooltip body | 80 | Brief reinforcement |
| Empty state body | 200 | Inviting + 1 CTA |
pt-BR translations may run +30%; design for the longer language and truncate gracefully (no clipping).
R2 — Vocabulary
Canonical terms
| Koder canonical | Avoid |
|---|---|
| Sign in (not "log in") | "Login", "Log in" |
| Sign out | "Logout", "Log out" |
| Tenant / workspace | "Account" (ambiguous with billing) |
| You (second person) | "User", "Users" (third person) in UI |
| Save | "Submit" (for non-form workflows) |
| Cancel | "Discard", "Close" (use only for actually closing) |
| Delete | "Remove" (different semantics — remove from view, not data) |
| Undo | "Cancel" (after the fact) |
| Send | "Post" (in non-social contexts) |
Forbidden words
- "Simply", "just", "easy" — they trivialize user effort
- "Wrong", "invalid", "error" in body text — symbol + spec text per
user-facing-messages.kmd - "Whoops", "oops" — friendly to a fault; use serious calm
- "Click", "tap" — surface-neutral language ("Select", "Open")
- "Awesome", "great", "perfect" as success acknowledgment — over-praise
R3 — Capitalization
Sentence case for everything except product names and proper nouns:
- ✅ "Save changes"
- ❌ "Save Changes"
- ✅ "Sign in to Koder"
- ❌ "Sign In To Koder"
Exception: company/product names retain Title Case ("Koder Design", "Koder Talk", "Koder Flow").
R4 — Punctuation
- Period at end of complete sentences in body text
- No period at end of short titles / buttons / labels / list items
- Em dash with thin spaces: " — " (HTML entity
—) - Curly quotes for prose: " '
- Straight quotes for code only
- Oxford comma: yes (en-US convention; pt-BR follows standard pt comma rules)
R5 — Numbers
- Cardinal numerals < 10: spell out in prose ("nine items", "ten items")
- ≥ 10: numerals ("10 items", "1,234 items")
- Counts in UI labels: always numerals ("3 unread")
- Time: 24h format in en-US ("14:32"); pt-BR same
- Dates: ISO 8601 in technical contexts; localized friendly form in UI
R6 — Internationalization
- Strings in code: en-US source of truth
- Translations: human-translated for pt-BR; machine fallback marked visibly until human pass
- ICU keys per
i18n/contract.kmdR6 - Plurals: ICU
{count, plural, one {1 file} other {# files}} - Variables in translations:
{name}, never positional%s
R7 — Accessibility
- Visible text MUST also be readable by screen readers (no CSS-tricks where the visible label differs from the semantic one)
- Icon-only buttons MUST have
aria-label - Error text MUST be associated with the failing field
(
aria-describedby) - Section titles MUST use semantic headings (
<h2>-<h6>, never styled divs)
R8 — Examples
Button label
| ❌ | ✅ | Why |
|---|---|---|
| "OK" | "Save" | Verb-specific |
| "Click here to save" | "Save" | Concise + neutral |
| "SUBMIT" | "Send message" | Sentence case + specific verb |
Error
| ❌ | ✅ |
|---|---|
| "Invalid input" | "Email must include @ — try name@example.com" |
| "Wrong password" | "We couldn't sign you in. Check your password or reset it." |
| "Server error" | "Couldn't save right now. Your changes are safe locally — we'll retry automatically." |
Empty state
| ❌ | ✅ |
|---|---|
| "No data" | "No projects yet — create your first to get started." |
| "Empty" | "You have no scheduled messages. Schedule one from any conversation." |
Cross-link
errors/user-facing-messages.kmd— error message formatpolicies/language.kmd— pt-BR / en-US per surfacei18n/contract.kmd— translation pipeline
References
specs/errors/user-facing-messages.kmdpolicies/language.kmdspecs/i18n/contract.kmd