| 1 | --- |
| 2 | name: ui-copy |
| 3 | description: Write the words a user reads on screen — button and menu labels, empty states, error and refusal text, tooltips, hint text, placeholders, dialog titles, toasts, status lines, onboarding panels. Read this BEFORE typing any string a person will read, including a one-word label, and before reviewing or rewriting existing copy. Triggers on writing or editing UI text, microcopy, error messages, empty states, confirmation dialogs, or any user-visible string in a component, view, or copy module. |
| 4 | --- |
| 5 | |
| 6 | # UI copy |
| 7 | |
| 8 | Two jobs. **Writing a string** is the common case and comes first. **Reviewing |
| 9 | existing strings** is the appendix at the end. |
| 10 | |
| 11 | Most rules here are quoted from published design systems and carry a source. |
| 12 | Where the guides genuinely disagree, that is recorded as a split rather than |
| 13 | resolved — pick per project and stay consistent. |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## Writing a string |
| 18 | |
| 19 | Work in this order. The order matters: the slot determines the budget, and the |
| 20 | budget does most of the work that taste otherwise has to. |
| 21 | |
| 22 | 1. **Name the slot.** Button, error, empty state, hint, tooltip, toast, title. |
| 23 | Each has a different published shape and length; they are not |
| 24 | interchangeable. |
| 25 | 2. **Take the budget** from the table below, before writing. |
| 26 | 3. **Write the shortest true version** that fits. |
| 27 | 4. **Run the two checks** (below). Most strings pass and are done. |
| 28 | |
| 29 | ### The two checks |
| 30 | |
| 31 | **Check 1 — can the reader act on it?** Cut anything the reader cannot act on. |
| 32 | |
| 33 | > "Avoid providing details that aren't essential for the user to know, such as |
| 34 | > how an action or process is performed." |
| 35 | > Do: "Preparing video…" Don't: "Buffering…" |
| 36 | > Do: "This command isn't supported on your phone." |
| 37 | > Don't: "This command is only supported on dual-core devices." |
| 38 | > — [Material 2](https://m2.material.io/design/communication/writing.html) |
| 39 | |
| 40 | **Check 2 — is it in the reader's terms or the system's?** |
| 41 | |
| 42 | > "**Use user-centered explanations.** Describe the problem in terms of user |
| 43 | > actions or goals, not in terms of what the software is unhappy with." |
| 44 | > — [Microsoft](https://learn.microsoft.com/en-us/windows/win32/uxguide/mess-error) |
| 45 | |
| 46 | Also: "Use words, phrases, and concepts familiar to the user, rather than |
| 47 | internal jargon" ([NN/g heuristic |
| 48 | #2](https://www.nngroup.com/articles/ten-usability-heuristics/)). |
| 49 | |
| 50 | ### Budgets |
| 51 | |
| 52 | Verified numbers only. Where a guide publishes no number, none is invented. |
| 53 | GOV.UK, Polaris, and Mailchimp publish **no** length budget for buttons, |
| 54 | errors, or hint text — do not cite one to them. |
| 55 | |
| 56 | | Slot | Budget | Source | |
| 57 | |---|---|---| |
| 58 | | Action label / CTA | 1–2 words | Atlassian, Carbon, Apple | |
| 59 | | Message or dialog title | 3–4 words, excluding a/an/the | Atlassian | |
| 60 | | Message body | 1–2 sentences | Atlassian | |
| 61 | | Empty-state description | sentences under 14 words | [Stripe](https://docs.stripe.com/stripe-apps/patterns/empty-state.md) | |
| 62 | | Hint text | a single short sentence, no full stop | [GOV.UK](https://design-system.service.gov.uk/components/text-input/) | |
| 63 | | Icon tooltip | one- or two-word description | Carbon | |
| 64 | | Tooltip (general) | 60–75 characters | Apple | |
| 65 | | Toast / inline error | 2 / 3 lines maximum | Carbon | |
| 66 | | Notification | title <29, collapsed <40, expanded <80 chars | Material 3 | |
| 67 | | Progress label / tag | 16 / 20 characters | Carbon | |
| 68 | | Any sentence | split if over 25 words | [GOV.UK](https://guidance.publishing.service.gov.uk/writing-to-gov-uk-standards/writing-guidelines/clear-language/) | |
| 69 | | Clauses per sentence | avoid more than three, better not more than two; one verb per sentence | [Microsoft](https://learn.microsoft.com/en-us/style-guide/global-communications/writing-tips) | |
| 70 | | Page title | ≤65 characters including spaces | GOV.UK | |
| 71 | |
| 72 | Leave room for translation: allow ~30% extra space, 200% for short strings |
| 73 | ([Microsoft](https://learn.microsoft.com/en-us/windows/win32/uxguide/text-ui)). |
| 74 | |
| 75 | --- |
| 76 | |
| 77 | ## Errors and refusals |
| 78 | |
| 79 | ### Structure: what happened, then how to fix it |
| 80 | |
| 81 | Unanimous across seven independent guides — GOV.UK ("explain what went wrong |
| 82 | and how to fix it"), Polaris, Carbon ("First, inform the user what has |
| 83 | happened, then provide guidance on next steps"), Atlassian, Microsoft |
| 84 | ("A problem. A cause. A solution."), NN/g heuristic #9, Apple. |
| 85 | |
| 86 | Apple's framing test: *"That password is too short"* is less helpful than |
| 87 | *"Choose a password with at least 8 characters."* |
| 88 | |
| 89 | ### The cause is required — but constrained |
| 90 | |
| 91 | **Do not delete the "why".** Microsoft lists a cause as one of three required |
| 92 | parts; Apple's alert title should describe "what happened, the context in which |
| 93 | it happened, and why"; Atlassian requires "the reason for the error"; NN/g |
| 94 | lists "concisely educate on how the system works" as a guideline. Polaris makes |
| 95 | it conditional: explain what happened behind the scenes "when it's helpful to |
| 96 | merchants or you can't offer a solution." |
| 97 | |
| 98 | Four constraints keep the cause from becoming an essay: |
| 99 | |
| 100 | - **User terms, not system terms** (Check 2 above). |
| 101 | - **Only if it changes what they do next.** If it does not, it fails Check 1. |
| 102 | - **Not if the fix already implies it.** > "Don't provide a solution if it can |
| 103 | be trivially deduced from the problem statement." — |
| 104 | [Microsoft](https://learn.microsoft.com/en-us/windows/win32/uxguide/mess-error) |
| 105 | - **Never invented.** "If you don't know the reason for an error, don't make |
| 106 | one up — just say that something's gone wrong and offer a solution." |
| 107 | (Atlassian). Polaris's fallback string: "Something went wrong. Refresh your |
| 108 | browser to try again." |
| 109 | |
| 110 | ### A refusal without an exit is a defect |
| 111 | |
| 112 | Carbon: "User actions are mandatory for error messages"; its Don't example is |
| 113 | the bare "Instance was not created." GOV.UK bans the no-exit strings outright: |
| 114 | *An error occurred*, *Answer the question*, *Select an option*, *Fill in the |
| 115 | field*, *This field is required*. |
| 116 | |
| 117 | ### Some refusals should not be error messages at all |
| 118 | |
| 119 | > "Do not use error messages to tell a user that they are not eligible or do |
| 120 | > not have permission to do something. Or to tell them about a lack of capacity |
| 121 | > or other problem the user cannot fix — because the problem is with the |
| 122 | > service rather than with the information the user has provided. Instead, take |
| 123 | > the user to a page that explains the problem… and provides useful information |
| 124 | > about what to do next." |
| 125 | > — [GOV.UK](https://design-system.service.gov.uk/components/error-message/) |
| 126 | |
| 127 | Before writing a permission, eligibility, or capacity refusal, ask whether the |
| 128 | dialog is the wrong container. |
| 129 | |
| 130 | ### Instruction vs description |
| 131 | |
| 132 | > "use an instruction for empty fields like 'Enter your name', but a |
| 133 | > description like 'Name must be 35 characters or less' for entries that are |
| 134 | > too long" |
| 135 | > — GOV.UK. Also: error messages should reuse the language of the question or |
| 136 | > label they belong to. |
| 137 | |
| 138 | ### Words the guides ban outright |
| 139 | |
| 140 | GOV.UK (errors): *forbidden*, *illegal*, *prohibited*, *you forgot*, *please* |
| 141 | ("implies a choice"), *sorry* ("does not help fix the problem"), *valid* / |
| 142 | *invalid*, *oops*. |
| 143 | |
| 144 | Microsoft substitutions: error, failure → **problem**; failed to → **unable |
| 145 | to**; illegal, invalid, bad → **incorrect**; abort, kill, terminate → **stop**; |
| 146 | catastrophic, fatal → **serious**. |
| 147 | |
| 148 | NN/g: "Avoid humor since it can become stale if users encounter the error |
| 149 | frequently." |
| 150 | |
| 151 | --- |
| 152 | |
| 153 | ## Empty states |
| 154 | |
| 155 | **They need text.** This is the one place the "delete it" instinct is wrong. |
| 156 | |
| 157 | > "Do not default to totally empty states. This approach creates confusion for |
| 158 | > users, who may be left wondering if the system is still loading information |
| 159 | > or if errors have occurred." |
| 160 | > — [NN/g](https://www.nngroup.com/articles/empty-state-interface-design/) |
| 161 | |
| 162 | The most operational published spec, from |
| 163 | [Stripe](https://docs.stripe.com/stripe-apps/patterns/empty-state.md): |
| 164 | |
| 165 | ``` |
| 166 | Title State what's missing. Short phrase. No promotion, no explanation. |
| 167 | good "No successful payments." |
| 168 | bad "Try creating your first payment to get started!" |
| 169 | "No transactions yet." beats "No transactions" |
| 170 | Description When and how data will appear. Sentences under 14 words. Active voice. |
| 171 | Action Must answer the title (call-and-response): |
| 172 | "No customers yet." -> "Add customer", not "Get started" |
| 173 | Filtered empty is not empty. Render order: loading -> error -> empty -> content |
| 174 | ``` |
| 175 | |
| 176 | Carbon adds: write it as a positive statement — "Start by adding data assets" |
| 177 | beats "You don't have any data assets." Its one licence for near-silence is |
| 178 | narrow: supplementary text can go when next steps are impossible (an alerts |
| 179 | view with nothing triggered). No source authorizes zero pixels. |
| 180 | |
| 181 | --- |
| 182 | |
| 183 | ## Other slots |
| 184 | |
| 185 | **Buttons.** `{verb} + {noun}`, no articles — Polaris and Carbon give the |
| 186 | identical formula with the same exception list (Save, Close, Cancel, OK, Done, |
| 187 | Add, Delete). Atlassian: "Avoid articles in buttons, labels, and action-based |
| 188 | headings" — "Create password", not "Create a password". Name the outcome; |
| 189 | destructive actions use the destructive word. |
| 190 | |
| 191 | **Hint text.** Justified only for what the label cannot carry: how the data |
| 192 | will be used, where to find it, or an example of an unfamiliar format. One |
| 193 | short sentence, no full stop, no links (screen readers announce link text |
| 194 | without signalling it is a link). Longer help is **promoted** to page body |
| 195 | above the field, not deleted. Restating the label is the named failure mode — |
| 196 | NN/g's example is an info tip reading "Enter the city of your birth" beside a |
| 197 | field labelled *City of Birth*. |
| 198 | |
| 199 | **Placeholders.** Never a label, never the hint. Unanimous. GOV.UK renders none |
| 200 | at all; NN/g and Carbon permit one only in addition to a persistent visible |
| 201 | label. Do not put required information there. |
| 202 | |
| 203 | **Tooltips.** Never essential ("Don't use tooltips for information that is |
| 204 | vital to task completion" — NN/g; Carbon, Polaris, and Material agree), never |
| 205 | redundant with a visible label (Atlassian, Material, Apple, NN/g), and length |
| 206 | is a design smell: "Long tooltip content is hard to read and suggests the |
| 207 | information might need a more prominent placement" (Polaris); "If you need a |
| 208 | lot of text to describe a control, consider simplifying your interface design" |
| 209 | (Apple). |
| 210 | |
| 211 | **Icon-only controls.** Ambiguity escalates to a *visible* label, not a longer |
| 212 | tooltip. NN/g: "Icon labels should be visible at all times, without any |
| 213 | interaction from the user." Atlassian's five-second rule: if it takes more than |
| 214 | five seconds to think of an appropriate icon, use a text label. See the |
| 215 | accessibility floor below — the accessible name is not optional. |
| 216 | |
| 217 | **Link labels.** NN/g's four Ss — specific, sincere, substantial, succinct — and |
| 218 | deliberately **no word cap**: "link length is less important than a good link |
| 219 | description", illustrated with an approved eleven-word link |
| 220 | ([NN/g](https://www.nngroup.com/articles/better-link-labels/)). Do not trim a |
| 221 | link to fit a budget that was written for buttons. |
| 222 | |
| 223 | **Confirmations.** Routine and reversible actions get no confirmation and no |
| 224 | toast; offer undo instead. NN/g: "Do not use confirmation dialogs for routine |
| 225 | actions. Like in Aesop's fable, if you cry wolf too many times, people will |
| 226 | stop paying attention." Apple: "Avoid using an alert merely to provide |
| 227 | information… Avoid explaining alert buttons." Name the irreversible consequence |
| 228 | plainly; buttons name outcomes, not OK/Cancel. |
| 229 | |
| 230 | **Loading and status.** One gerund. Do not narrate stages unless the wait is |
| 231 | long and the stages are genuinely different. Do not over-toast: "For a user |
| 232 | that is saving a lot of items to their favorites, this can be a bothersome and |
| 233 | intrusive way of providing feedback" (NN/g). |
| 234 | |
| 235 | --- |
| 236 | |
| 237 | ## When there should be no text |
| 238 | |
| 239 | Text competes with text. NN/g heuristic #8: "Every extra unit of information in |
| 240 | an interface competes with the relevant units of information and diminishes |
| 241 | their relative visibility" — framed as signal (high informational value) versus |
| 242 | noise, not as word count. |
| 243 | |
| 244 | Delete or promote, in rough order of how often it goes wrong: |
| 245 | |
| 246 | 1. Hint text restating the label. |
| 247 | 2. A tooltip duplicating a visible label, or any tooltip on an unambiguous icon. |
| 248 | 3. A toast or status line narrating a change already visible on screen. |
| 249 | 4. An instructional paragraph above a form restating what the controls do. |
| 250 | 5. Prose in an empty state — shorten, do not remove. |
| 251 | |
| 252 | Deferring beats deleting where the information is real: "Initially, show users |
| 253 | only a few of the most important options. Offer a larger set of specialized |
| 254 | options upon request" ([NN/g progressive |
| 255 | disclosure](https://www.nngroup.com/articles/progressive-disclosure/)). |
| 256 | |
| 257 | Icon-vs-label, page structure, and disclosure are interface-design decisions |
| 258 | rather than copy decisions; this skill only covers the words. |
| 259 | |
| 260 | ### The accessibility floor — text that must not be cut |
| 261 | |
| 262 | _**Never remove these as "redundant". They are load-bearing for users you |
| 263 | cannot see.**_ |
| 264 | |
| 265 | - **Accessible name on any icon-only control.** WCAG 1.1.1: "If non-text |
| 266 | content is a control… then it has a name that describes its purpose"; 4.1.2 |
| 267 | makes it programmatically determinable. The name describes the *action*, not |
| 268 | the picture. If the icon sits beside a visible text label, the icon itself |
| 269 | takes `alt=""`. |
| 270 | - **Status messages.** WCAG 4.1.3 — deleting a status message is legal; |
| 271 | rendering it visually **without** a live region is not. Visually obvious is |
| 272 | explicitly not sufficient. |
| 273 | - **Error text.** WCAG 3.3.1: a detected input error "is identified and the |
| 274 | error is described to the user in text". 3.3.3 requires a correction |
| 275 | suggestion where one is known. |
| 276 | - **Field labels.** WCAG 1.3.1 requires a programmatically associated label; a |
| 277 | placeholder is neither persistent nor a label. A visually hidden label is |
| 278 | still announced. |
| 279 | - **Hover/focus content.** WCAG 1.4.13 — custom tooltips must be dismissible, |
| 280 | hoverable, and persistent; no interactive content inside them. |
| 281 | |
| 282 | Note WCAG 2.5.3 Label in Name does **not** apply to icon-only controls — it |
| 283 | constrains icon-plus-label pairs. |
| 284 | |
| 285 | --- |
| 286 | |
| 287 | ## Where the guides disagree |
| 288 | |
| 289 | Do not launder these into false consensus. Follow the project's existing |
| 290 | convention; if there is none, pick one and apply it everywhere. |
| 291 | |
| 292 | | Question | The split | |
| 293 | |---|---| |
| 294 | | Title vs sentence case | Apple and Salesforce mandate title case for buttons and menu items; Microsoft, Material, Carbon, Atlassian, Polaris, GOV.UK all mandate sentence case | |
| 295 | | Ellipsis on a button that opens a dialog | Apple requires it; Material forbids it | |
| 296 | | Em dash | Material restricts ("best avoided in UX writing"); Microsoft and Mailchimp recommend it; Polaris and Atlassian allow it conditionally; Carbon and GOV.UK are silent | |
| 297 | | Em dash spacing | Polaris unspaced, Atlassian spaced | |
| 298 | | Ranges | Polaris en dash ("2006–2013"); Atlassian and GOV.UK spell "to" | |
| 299 | | "we" in error messages | Atlassian requires it (avoids blaming the user); Polaris forbids it unless the company is at fault | |
| 300 | | Period on a fragment title | Everyone says no; Stripe's empty-state spec says yes | |
| 301 | | Colon after a field label | Material says skip; Win32 requires it for assistive tech | |
| 302 | | Explaining internals | Atlassian bans technical information in the message; NN/g endorses "concisely educate on how the system works" | |
| 303 | | Politeness | Material bans *please* / *sorry* / *thank you* in errors; Carbon permits *please* when the user is inconvenienced; Microsoft permits *sorry* only for a serious problem | |
| 304 | |
| 305 | The one convergent punctuation rule, reached independently by Polaris and |
| 306 | Atlassian: **prefer two sentences over a dash.** Polaris — "Use an em dash only |
| 307 | if you can't make your message clearer by splitting it into two sentences." |
| 308 | |
| 309 | **Periods**: no period on fragments, headings, titles, tooltips, field |
| 310 | descriptions, or list items of three words or fewer; periods are fine once |
| 311 | there are two or more sentences. Microsoft, Polaris, Atlassian, Material, and |
| 312 | Apple agree (Stripe's empty-state title is the documented exception). |
| 313 | |
| 314 | --- |
| 315 | |
| 316 | ## Appendix: reviewing existing copy |
| 317 | |
| 318 | Use this on a review pass, not while writing. |
| 319 | |
| 320 | **The register tell.** Copy drafted by a language model tends to state a fact |
| 321 | and then append a justification for it, in the voice of a design document |
| 322 | rather than a product. The joints are an em dash, a `, so`, or a bare |
| 323 | appositive: |
| 324 | |
| 325 | ``` |
| 326 | "Reconnecting — this transcript may be behind." |
| 327 | "No host is connected, so there is no conversation to show." |
| 328 | "A chat runs in a project's track, so there is nowhere for one to go." |
| 329 | ``` |
| 330 | |
| 331 | Each fails Check 1 or Check 2 above: it explains mechanism the reader cannot |
| 332 | act on, in the system's terms rather than theirs. Note the em dash itself is |
| 333 | **not** the defect — it is only where the clause attaches, and two respected |
| 334 | guides recommend em dashes. Judge the clause, not the punctuation. |
| 335 | |
| 336 | *This section is the one part of this skill with no published prior art behind |
| 337 | it. Corpus work on LLM prose covers long-form writing only, and no design |
| 338 | system addresses AI-drafted strings. Treat it as a working hypothesis.* |
| 339 | |
| 340 | **Other things to catch on review:** |
| 341 | |
| 342 | - A docket of non-events: "This message was not sent." "Nothing was deleted." |
| 343 | Prefer "Not sent", "Couldn't rename track". |
| 344 | - Reassurance for a loss the reader can already see. Reassure only when the |
| 345 | loss would be invisible and is real. |
| 346 | - A second clause that restates the first (Microsoft's trivially-deducible |
| 347 | rule). Note that a two-sentence message is not itself a defect — Atlassian |
| 348 | prescribes exactly two, "the most likely cause or the simplest solution in |
| 349 | the first sentence… an alternative backup solution in the second". Judge |
| 350 | whether the second sentence adds an action, not whether it exists. |
| 351 | - Blaming a subsystem: "This session's host has not reported a model" → |
| 352 | "No model available". |
| 353 | |
| 354 | **Do not overcorrect.** These are correct and should be left alone: bare labels |
| 355 | ("Cancel", "Copy"/"Copied", "Running"/"Done"/"Failed", "no matches"); gerund |
| 356 | plus ellipsis for waits ("Attaching…"); lowercase fragments in dense lists; and |
| 357 | a detail line carrying a genuine gotcha the reader cannot see and would guess |
| 358 | wrong about ("Ignored paths are not searched."). |
| 359 | |
| 360 | --- |
| 361 | |
| 362 | ## Where copy lives |
| 363 | |
| 364 | Before editing a string, find out whether it is centralized. If a project keeps |
| 365 | its sentences in a copy module, a wording change is one edit; if labels are |
| 366 | inlined at call sites, the same change is N edits and the duplicates will |
| 367 | drift. Say which you are dealing with rather than silently editing one of two |
| 368 | copies. |
| 369 | |
| 370 | ## Source caveats |
| 371 | |
| 372 | Polaris, Carbon, Material 3, Atlassian, and Salesforce content pages are |
| 373 | client-rendered, gated, or dead at their canonical URLs; several quotes above |
| 374 | come from the design systems' own repositories or Wayback captures rather than |
| 375 | the live site. Microsoft has no current error-message page — its |
| 376 | cause-and-solution structure is from Win32 guidance carrying a "not updated" |
| 377 | banner. The Polaris toast page is formally deprecated. |