Writing Interface Strings
Default posture: every string names the object and the outcome, in the reader's words, in sentence case, with nothing hedged. Two structural laws carry most of the work — a button says what will happen when it is pressed, and an error says what happened and what to do next, in that order. Generic strings are the single loudest signal that nobody designed the surface: Submit, Invalid input and Something went wrong are three different ways of refusing to say anything. ui-states decides when an empty, loading, error or offline state appears and how it is anchored; this skill writes the words that go inside it — if the question is "when does this show" or "skeleton or spinner", that is ui-states, not this. Field label placement and validation timing are forms. The type the strings are set in is typography.
Read the product's existing strings before writing one. Detect the established voice and copy it: sentence case or title case, Sign in or Log in, you or the workspace name, whether errors address the user or describe the system. Detect the i18n mechanism too — a message catalog, next-intl, or strings hardcoded in JSX — and add new strings where the existing ones live. Introducing a second word for the same object, or a second casing convention, costs more than any individual string is worth; if the app already says Log in, write Log in even when Sign in is better.
Quick Reference
| Open this | When |
|---|---|
| patterns.md | You need the actual replacement string for a specific surface — a destructive confirm, a validation error, an empty state, a toast — rather than the rule behind it. |
Core Principles
- Label the outcome, not the mechanism. The button is the last thing read before commitment, so it is where the consequence belongs:
Save changesoverSubmit,Delete 3 projectsoverConfirm,Send inviteoverOK. Exception: conventions the platform has already taught —Cancel,Done,Sign in,Next— stay conventional; inventing a fresh verb for a learned control costs comprehension and buys nothing. - Sentence case throughout product UI, and never the word "please". Title case on anything longer than two words reads like a legal document, and "please" in an imperative is an apology for the product existing:
Confirm, notPlease confirm your selection before proceeding. Exception: proper nouns keep their casing, and a marketing surface may use title case if the brand system says so — one convention per product, written down. - An error is two clauses: what happened, then what to do. In that order, because a reader who already knows the fix stops reading.
Your email must include an @ symbolbeatsInvalid input. Never shipOops,Uh oh, or a bareSomething went wrong. Exception: a fault the user cannot fix — a500, an upstream outage — drops the instruction and instead states that it is not their fault, whether their work is safe, and one escape: retry, contact, or go back. - Never call the input invalid; name the constraint that was missed. "Invalid" describes the system's verdict, not the user's problem:
Passwords need at least 12 charactersoverPassword invalid. Exception: security-sensitive failures stay deliberately vague —That email and password don't matchmust never reveal which of the two was wrong. - A destructive confirm names the object and the consequence in the button; the dialog title asks the question.
Are you sure?withOK/Cancelcontains no information about what is about to happen, so people learn to click through it. Write the title asDelete these 3 projects?and the button asDelete 3 projects. Exception: if the action is reversible, do not confirm at all — perform it and offerUndo. A confirm on a reversible action is what trains people to dismiss the confirms that matter. - An empty state carries three things: what lives here, why nothing is here yet, and one action.
No invoices yet. Invoices appear here once you bill a customer.plus aCreate invoicebutton.No items foundis a status code with a font. Exception: a filtered empty result is not an empty state — it names the query and offers to clear the filter, andsearch-filterowns that recovery path. - Write real content, never lorem. guidelines.sh's Content Authenticity rule: a layout that has only ever held
Lorem ipsumhas never been tested against the longest real name, the empty value, the single-character value, or a 40-character German compound — all four of which ship. Exception: when the string is genuinely unknown, use the worst realistic case as the stand-in — the longest plausible value in the real language — never latin. - Tone descends as the stakes rise. Warmth is available in the rare tier — a first success, a completed setup — and
delightdecides whether that moment earns a flourish at all. At an error, a destructive confirm, a billing failure or a data-loss warning the voice goes flat: no exclamation marks, no jokes, no personifying the software. Exception: a warm brand may keep the warmth in the surrounding sentence, but the verb and the consequence stay literal —Your card was declined, neverYour card had a little trouble. - Use the reader's vocabulary, not the schema's.
Send messages in the backgroundoverEnable async relay mode; rename the nav item when search logs show people typinginvoiceswhile the label saysBilling. Exception: audiences with their own domain language — developers, clinicians, accountants — wherewebhookis the reader's word and simplifying it to "automatic message" makes the string worse. - Front-load the word that identifies the string. Toasts, notification titles, list rows and table cells are read at a glance and frequently truncated, so the identifying word cannot sit mid-clause:
Export ready — download it, not a sentence with "export" buried in the middle. Exception: long-form help and documentation copy, which reads better with a normal lead-in because nothing there is clipped.
A length check for anything time-limited: people read interface copy at roughly 200–250 words per minute, so a toast must be readable inside the dwell time before it leaves. notifications owns that dwell time; this skill owns making the string short enough to survive it.
Smell / Fix
| Smell | Fix |
|---|---|
Submit / OK / Confirm on a primary button |
Name the outcome: Save changes, Delete 3 projects |
Something went wrong |
Say what went wrong and give one action |
Invalid input |
Name the constraint that was missed |
Are you sure? with OK / Cancel |
Title asks the specific question, button states the specific action |
| A confirm dialog on a reversible action | Remove the dialog, add Undo |
No items found in an empty state |
What lives here, why it is empty, one action |
| Lorem ipsum anywhere in a component | Real strings, including the longest and shortest real case |
Oops! / Uh oh! / an exclamation mark on a failure |
Flat, literal, no punctuation theatre |
| Title Case On A Long Button Label | Sentence case |
| "Please confirm before continuing" | Drop "please"; the imperative is the whole string |
| Internal jargon in a user-facing label | The word the user searches for |
| Two words for the same object in one product | Pick one, replace the other everywhere |
| A toast nobody can finish reading | Cut to the identifying clause; front-load the noun |
Output Format
Writing new strings: a | Surface | String | table, one row per state the surface can be in — including the empty, error and loading text — plus the longest and shortest realistic value for anything user-supplied that the layout must survive. Revising existing strings: | Location | Current | Revised |, citing the principle number in one clause, with no per-row editorialising. If the request is a review with a Block or Approve verdict rather than a rewrite, that is ui-review and its | Before | After | Why | format applies instead.
Checklist
- Every button names its outcome, not its mechanism
- Sentence case throughout; no "please" in an imperative
- Every error says what happened and what to do next, in that order
- No string calls the user's input "invalid" without naming the constraint
- Destructive confirms name the object and the count; reversible actions offer
Undoinstead - Every empty state has context and exactly one action
- No lorem ipsum; the longest and shortest real values were tested
- Failure copy is flat and literal — no jokes, no exclamation marks
- Product vocabulary matches the reader's, and one object has one name
- Truncatable strings front-load their identifying word
- New strings live where the existing strings live, in the same catalog