# Words in the interface

> Write labels, errors and status copy that guide, build strings that survive translation, and name components by what they do.

Module: Components and states · Level: beginner · 8 min · https://school.agents.org.in/design/components/words-in-the-ui

Set the count to 1. Then to 0. Then switch the locale.

*[Interactive demo: The glued sentence breaks at 1 and 0. The ICU message and Intl never do.]*

"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. Microcopy (The small words in an interface: button labels, error messages, empty states, tooltips, status lines. Small, read constantly, and a big part of how a product feels), 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 (avoid):**

Something went wrong.<br />Error: 500 Internal Server Error

  **After (do this):**

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-label` follows 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 ICU message (A standard format for writing one translatable sentence with placeholders and plural rules inside it, like {count, plural, one {# file} other {# files}}, instead of gluing strings together in code) with placeholders:

```text
inbox.count = {count, plural,
  =0 {No new messages}
  one {# new message}
  other {# new messages}
}
```

And format every number, date and list through `Intl` (The browser's built-in formatter for numbers, money, dates, relative times and lists in any language. It knows that Germans write 1.234,50 € and Americans $1,234.50) 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 |
|---|---|
| Tooltip (A small label that appears on hover or focus to explain an element. It can't hold anything you click, because the pointer can't move into it without closing it) vs popover (A floating panel that opens when you click something and stays open until dismissed. Unlike a tooltip, it can hold links, buttons or a small form) | 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)?

**Copy and strings pass (paste into your agent):**

```text
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.
```

**Check yourself:** A floating box needs to hold a 'Read the docs' link. Which component is it?

- [ ] A tooltip, because it floats next to the element
- [x] A popover, because it holds interactive content and opens on click
- [ ] A badge, because it is attached
- [ ] A toast, because it is small

*Why:* A tooltip can't take focus, so a link inside it is unreachable. Anything with a link or button inside is a popover.

## In the skill

- https://school.agents.org.in/skill/components/copy-voice.md
- https://school.agents.org.in/skill/components/strings-and-locale.md
- https://school.agents.org.in/skill/components/component-confusables.md
