Words in the interface
beginner·8 min·Markdown for agents
What this lesson teaches
Write labels, errors and status copy that guide, build strings that survive translation, and name components by what they do.
Set the count to 1. Then to 0. Then switch the locale.
Set the count to 1 or 0 and watch the glued sentence break. The Intl rows follow the locale without one line of formatting code of your own.
"You have 1 new messages" is what you get when a sentence is glued together around a number. It is a small thing, and people notice it every single time. , the small words on buttons, errors, empty states and tooltips, is design. It ships every day, and the wrong word quietly ages the product.
The voice rules
- Active voice. "Saved your changes" beats "Your changes have been saved".
- Talk to "you". "You can invite 3 more people", not "Users can…". In errors, "we" sounds like deflection.
- Specific beats vague. "Couldn't connect to the server" beats "Something went wrong".
- Skip the throat-clearing. "Saved." beats "Awesome, your changes were saved!".
- Numerals for counts. "3 projects", even small ones. A non-breaking space keeps "3 MB" together.
- Real characters. The ellipsis
…, curly quotes, an en dash for ranges (5–10).
Pick one capitalization for buttons and headings, Title Case or sentence case, and hold it across the whole product. Mixing them on one screen is the mistake, not either choice.
Don't be cute in errors. "Oops! Something tripped on the wires!" is funny once and annoying the tenth time.Errors that guide the exit
A good error answers three questions: what went wrong, why (if it helps), and what to do next.
✕ Before
Something went wrong.
Error: 500 Internal Server Error
✓ After
Changes not saved. Check your connection and try again.
Status copy stays parallel
Async states read best as one family: same object, same order.
| State | Pattern | Example |
|---|---|---|
| In progress | -ing verb + object | Saving changes… |
| Done | object + past tense, no "successfully" | Changes saved |
| Failed | what did not happen + next step | Changes not saved. Check your connection and try again. |
| Unavailable | why, beside the control | Add a payment method to publish |
| Partial | both counts | 9 of 12 files uploaded. Retry 3 failed files |
Labels that do their job
- Buttons start with a verb naming the action: "Send", "Save draft", "Delete project". Never "OK" or "Let's go!". An icon button's
aria-labelfollows the same rule. - Links name their destination: "Read the billing docs", not "Click here". Two "Learn more" links on one page each get a suffix.
- Toggles describe the ON state: "Send read receipts", never "Don't send read receipts", which turns the switch into a double negative.
- Match the device: "tap" on touch, "click" with a mouse, "select" when it could be either.
- A placeholder is an example, like
name@example.com, never the label and never an instruction.
Strings that survive translation
Before writing new copy, find where the existing copy lives (the t( calls and the locales/*.json files) and list the words already in use. If the menu says "Archive", the toast does not say "Move to storage". One thing, one name.
Then build every sentence whole. Word order changes between languages, and many languages have more than two plural forms, so n === 1 ? "" : "s" is wrong outside English. Use one with placeholders:
inbox.count = {count, plural,
=0 {No new messages}
one {# new message}
other {# new messages}
}And format every number, date and list through with the user's locale:
| Value | Call |
|---|---|
| Money | Intl.NumberFormat(locale, { style: "currency", currency }) |
| Date | Intl.DateTimeFormat(locale, { dateStyle: "medium" }) |
| "Yesterday" | Intl.RelativeTimeFormat(locale, { numeric: "auto" }) |
| "Maya, Jon and Ana" | Intl.ListFormat(locale, { type: "conjunction" }) |
toLocaleDateString() with no locale, or a date built from getMonth(), is the bug. Translated strings also grow, a one-word button most of all, so leave room.
Naming components by behavior
Words matter for components too. Some look alike, and picking by looks breaks what users expect. Pick by behavior, not appearance.
| Pair | The line between them |
|---|---|
| vs | Can it hold a link or button? A tooltip is a hover label and can't; a popover opens on click and can |
| Badge vs tag | A badge is pinned on and read-only (the "3" on an inbox); a tag is standalone and you can select or remove it |
| Dialog vs sheet vs drawer | A dialog interrupts, centre-screen, for a decision now; a sheet slides from an edge; a drawer pulls up from the bottom. Sheets and drawers are for secondary, dismissible surfaces |
Three questions settle it: must they interact inside it (popover)? Can they act on it (tag)? Must they decide now (dialog)?
Review the copy in this screen:
- Inventory existing nouns and verbs first (t() calls, locales/*.json) and reuse them; one name per thing.
- Buttons start with a verb; links name their destination; toggles describe the ON state; placeholders are examples.
- Errors: what failed + the next step. Status copy parallel: "Saving changes…" → "Changes saved" → "Changes not saved. Check your connection and try again."
- No sentence glued around a variable; plurals through ICU {count, plural, ...}; numbers, dates and lists through Intl with the user's locale.
- Keep one capitalization style for all buttons and headings.Quick check
A floating box needs to hold a 'Read the docs' link. Which component is it?
In the skill
The nodes your agent reads when it does this work.
- Copy and voiceActive voice, Title Case for headings + buttons, "&" over "and", 2nd person, numerals for counts, error messages guide the exit.
components/copy-voice - Strings and localeInventory the existing strings before writing one; one noun per object, one verb per action. Build sentences whole in ICU with real plural categories, format every number, date and list through Intl, start buttons with a verb, make links name their destination, label toggles for the ON state, and keep in-progress and done strings parallel.
components/strings-and-locale - Component confusablesThe component pairs people reach for interchangeably but shouldn't — tooltip vs popover, badge vs tag, sheet vs drawer vs dialog. Pick by behaviour, not by looks.
components/component-confusables
Connected lessons
What this lesson builds on and leads to, across every course.