Skip to content

Skills · experience-design

ux-writing

Write the interface copy for a flow or component — buttons, labels, hints, errors, empty states, confirmations, loading and success messages — as a string table where each string has its context, a character budget, a recommended version and one variant, with error messages that say what happened and what to do next, and a terminology list so the same thing has the same name everywhere. Use when the user wants UI copy, microcopy, button text, error messages, empty states, "write the strings for this screen", or "our wording is inconsistent".

Pack experience-designTier standardJob Design the experiencev0.1.0

npx skilldrop-cli install ux-writing
Other ways to install

With the sibling skills it hands off to:

npx skilldrop-cli install ux-writing --with-related

The whole pack it belongs to:

npx skilldrop-cli install --pack experience-design

By hand, from a clone of the repo:

cp -R packs/experience-design/skills/ux-writing ~/.claude/skills/

In Claude Code, with the experience-design plugin installed, invoke it as:

/experience-design:ux-writing

Try it

A realistic prompt from the skill's acceptance evals:

Write the UI copy for Acme Bank's 'add a payee' flow in our mobile app. Steps: enter payee name, sort code and account number; we run a name check against the bank's records (match, close match, no match); then confirm and add. Users can also see an empty Payees list before they've added anyone. Our style guide says: plain, calm, second person, no exclamation marks.

What a good result looks like

Example output

the copy for a team invite flow · the skill's own worked example, source

Input

We're adding "invite teammates" to Northwind Boards (project management SaaS). Flow: Team page → Invite button → modal with an email field (multiple, comma separated) and a role picker (Admin / Member) → send. There's also a Pending invites tab where you can revoke an invite. Right now the modal says "Add users" and the button says "Submit", and the Team page calls them "members" while the billing page says "seats". Voice: friendly but not cute. Web only, English only for now.

Output

Interface copy: team invite flow

Voice: friendly but not cute (given) → plain, "you", sentence case, no exclamation marks Platform: web · Localised: no, so budgets have no growth allowance Assumptions to confirm first:

  • There is a per-send invite limit ({limit} below, value not given).
  • Admins can manage billing and members, and Members can't. The role hint depends on this, so confirm it before shipping.
  • Invites go out by email with a join link, and revoking one kills the link without emailing the invitee.

Character counts include spaces and count each {placeholder} as written. Leave room for the real value: an email address can run 30+ characters.

String table

IDScreenSlotContextBudgetRecommended (count)Variant (count, why)
invite.titleInvitepage titleOpens from Team › Invite40"Invite people to your workspace" (31)"Invite teammates" (16, shorter)
invite.email.labelInvitelabelEmail field30"Email addresses" (15)"Emails" (6, shorter)
invite.email.hintInvitehintUnder the email field80"Separate addresses with commas. You can invite up to {limit} at once." (69)"Add one or more emails, separated by commas." (44, shorter; drops the limit)
invite.role.labelInvitelabelRole picker30"Role" (4)"Their role" (10, warmer)
invite.role.hintInvitehintUnder the role picker80"Admins can manage billing and members. Members can use the workspace." (69)"Choose what they can do in this workspace." (42, shorter; less explicit)
invite.submitInvitebuttonPrimary action20"Send invites" (12)"Invite" (6, shorter)
invite.error.email_formatInviteerrorAn address isn't a valid email100"“{value}” isn't an email address. Check it for typos, like a missing @." (71)"Check “{value}”. It needs an @ and a domain, like sam@northwind.example." (72, more explicit)
invite.error.already_memberInviteerrorAddress belongs to an existing member100"{email} is already in this workspace. Remove it to send the other invites." (74)"{email} is already a member, so we won't invite them again." (59, warmer; no action)
invite.error.limitInviteerrorMore addresses than the limit100"You can invite up to {limit} people at once. Remove {excess} to continue." (73)"That's more than {limit}. Send these in two batches." (52, shorter)
invite.error.networkInviteerrorRequest failed or timed out100"We couldn't send the invites. Check your connection and try again." (66)"Invites not sent. Try again in a moment." (40, shorter)
invite.loadingInviteloadingAfter pressing Send invites40"Sending invites…" (16)"Sending {n} invites…" (20, more explicit)
invite.successInvitetoastAll invites sent60"Invites sent to {n} people" (26)"{n} invites sent. They'll get an email." (39, more explicit)
pending.empty.titlePending invitesemptyNo pending invites40"No pending invites" (18)"Nobody's waiting to join" (24, warmer)
pending.empty.bodyPending invitesemptyUnder the empty title100"People you invite show here until they accept." (46)"Invites you send will appear here." (34, shorter)
pending.empty.actionPending invitesbuttonEmpty state action20"Invite people" (13)"Send an invite" (14, warmer)
pending.revoke.titlePending invitesdialog titleRevoke an invite40"Revoke invite for {email}?" (26)"Revoke this invite?" (19, shorter; drops the name)
pending.revoke.bodyPending invitesdialog bodyRevoke dialog160"The link in their email will stop working. You can invite them again later." (75)"They won't be able to join with this invite. You can send a new one any time." (77, warmer)
pending.revoke.confirmPending invitesbuttonDestructive confirm20"Revoke invite" (13)"Revoke" (6, shorter)
pending.revoke.cancelPending invitesbuttonDismiss20"Keep invite" (11)"Cancel" (6, shorter; less clear)

Recommendations, briefly: use the recommended column throughout. "Send invites" says what the button does where "Submit" doesn't. The already-member error keeps the action ("Remove it…") because the variant leaves the user unsure whether the other invites went. "Keep invite" beats "Cancel" in the revoke dialog, because "Cancel" next to "Revoke invite" reads as "cancel the invite".

Terminology

ConceptUseAvoidNotes
A person with accessmemberuser, seat, teammate (in UI labels)The billing page says "seats". Change it to "members", or explain "you pay per member". "Teammates" only in the page title variant.
The shared spaceworkspaceteam, account, organisationThe Team page title already says "Team". Rename it to "Members", or flag it for information-architecture.
Asking someone to joininvite (verb and noun)add, add usersReplaces "Add users" in the current modal
Withdrawing an inviterevokecancel, deleteKeeps "Cancel" free to mean "close this dialog"
Permission levelrolepermission, access levelRole names: Admin, Member

Open questions

  • What is {limit}, the most invites per send? The hint and the limit error need it.
  • Does an invite expire? If so, add an invite.expired error for the invitee's landing page and say the expiry in the success toast.
  • Can a Member invite, or only an Admin? If only Admins can, Members need a disabled-state hint on the Invite button.
  • Should the billing page change from "seats" to "members"? That's a product decision, not a copy one.

How it's checked

Measured against plain Claude Code (2026-10-23): it met 81% of its checks, against 60% without it, and a blind judge preferred the agent without it. How skills are checked

21 assertions across 3 acceptance evals, and 8 trigger queries. Every skill's checks

What the output must do
  • Every slot has a string, including an error for each field rule, each name-check result, a network failure, the empty Payees list, loading and success
  • Every string shows a character count against a stated budget, and no recommended string exceeds its budget
  • Every error says what happened and what to do next, names the field, and does not blame the user
  • Primary buttons are verb plus object; none says Submit, OK or Yes
  • Each string has one labelled variant and a recommendation
  • A terminology list picks one term per concept (e.g. payee vs recipient) with terms to avoid, and the strings follow it
  • No exclamation marks, matching the given style guide
  • Does not invent limits, fees, processing times or policies not in the prompt; unknowns are placeholders listed as open questions
  • Every button fits 16 characters, every title fits 28 characters and every body fits 90 characters, as screens.md requires
  • Uses one name for the unit (for example 'charger') everywhere, with none of 'station', 'charge point' or 'pump' left in the strings
  • The E43 message does not say the card has expired; it says the payment wasn't approved and what the person can do, such as trying another card
  • Each error message says what happened and what to do next, with the code shown at most as a small reference rather than as the whole message
  • Errors the user can't fix themselves (E51 ground fault, E60 network) point them to another option or support, and the user-fixable E12 does not send them to support
  • Does not hardcode a price per kWh and does not invent a support phone number or URL; the price comes from the system and the number is left as a placeholder
  • Covers all five screens and all five error codes
  • The full-class message says plainly that the class is full and offers the waitlist (or another class) as the next step
  • No error code or technical wording in the message the person sees
  • Button text is short (a few words) and says what will happen when tapped
  • Copy about the waitlist matches what the studio said: an email goes to the first person on the waitlist; it does not promise a guaranteed place, a text message or automatic booking
  • Does not invent features, prices or policies the studio didn't mention
  • Gives just the text with a couple of options, short as asked
When it should and shouldn't activate

Should activate:

  • Write the button labels and error messages for our signup form
  • We need empty states, loading and success messages for the file upload screen
  • Our app calls the same thing a project, a board and a space — fix the microcopy and give us a terminology list
  • Rewrite these error messages so people know what to do: 'Invalid input', 'Error 500', 'Request failed'

Should leave to another skill:

  • Plan the content for our benefits page from user needs: what questions it answers and what to cut
  • Write a getting-started guide for new admins setting up the product
  • Set up our brand kit from this logo and our tone-of-voice guidelines
  • Audit this checkout page for WCAG 2.2 AA issues

Hands off to

Related skills

brand-kit, content-design, design-system-spec, guide-builder

Source

SKILL.md · the whole folder on GitHub.