Writing product copy
Voice, microcopy, empty states, error messages and enum labels — the words in the product, as opposed to the words on the landing page. Use when writing any user-visible string.
Generated from
.claude/skills/writing-product-copy/SKILL.md, which ships in every project created from this template. Your agent loads it on demand; this page is the same text. Edit the skill, not this page.
Copy is the half of the interface people actually read, and it is the half
nothing checks. Three products built on this template shipped raw database
enum values into badges (no_show, in_progress), left the example dashboard
text in place — "Signed in as ff8e2843@example.test — you are the owner of
this workspace." — and gave every empty state three words and no explanation.
Every gate was green.
Voice
Write like a competent colleague explaining something, not like a system reporting a condition.
- Plain, specific, unhedged. "We couldn't save that — the name is already taken." not "An error occurred while processing your request."
- Second person for the reader, first person plural for you. "Your plan ends on 3 March." / "We've logged it."
- No exclamation marks, no "Oops", no "Whoops". A person who just lost work is not cheered up by punctuation.
- Say the noun. "Delete Acme Corp?" not "Delete this item?" — naming the record is the only thing standing between a misclick and the wrong row.
Microcopy that carries weight
| where | the rule |
|---|---|
| Buttons | a verb and its object: "Create invoice", not "Submit" |
| Destructive confirm | name the record, and say what cannot be undone |
| Page description | one sentence saying what this page is FOR — the cheapest usability win available, and the first thing skipped |
| Field hint | say the unit or the format when it is not obvious ("In cents — 4800 is $48.00") |
| Toast | what happened, in past tense: "Deleted Acme Corp." |
Empty states: two problems, two sets of words
A filtered-empty list and a genuinely empty account are not the same screen.
- Filtered empty — "No invoices match those filters." plus a way out. Never offer "create your first" here; the reader has twenty and mistyped a search.
- Never had any — say what the thing is for and why someone would make one, then the action. "No invoices yet." on its own reads as a bug.
Both get an icon. A dashed box with three words in it looks broken.
Error messages
Three parts, in this order: what happened, why if you know, what to do next.
- "We couldn't send the invitation. The email address bounced. Check the address and try again."
- Not: "Failed to send invitation." — which of the three did that answer?
Never surface an exception message to a user. AppError codes exist so the
message you wrote is the message they see, and INTERNAL is deliberately the
one whose text is withheld.
Enum labels
Database values are snake_case because Postgres is; readers are not.
formatEnumhumanises the common case:in_progress→In progress.- Override with an explicit map where automation gets it wrong — "SLA" not "Sla", "No-show" not "No show" if that is your domain's spelling.
- The label lives in the feature's badge module next to the tone map, so the row, the detail page and the filter all say the same word.
Placeholder copy is a bug
This template ships example strings on purpose so a fresh scaffold is not blank. They are not yours until you replace them. Before shipping, grep for:
this starter,Your product,example.testSay what,Tell people,goes here— the config's placeholder phrasing- Any sentence explaining what the template does rather than what your product does
If a string on your production site mentions a starter kit, a reader has learned something about the product you did not intend to tell them.
Designing app UI
Which application component to use for which job, how to lay out a page, and the hierarchy, density and copy rules that make a screen look finished. Use when building or reviewing any signed-in screen.
Marketing and SEO
The public site — marketing.ts as the single copy declaration, which block to use where, the two landing layouts, and the SEO helpers (page metadata, canonicals, JSON-LD, sitemap). Use when editing the landing page, adding a public page, or changing anything a crawler sees.