Claude Skill

sota-ux-writing

State-of-the-art UX writing and product-copy guidance (2026) covering voice and tone systems, plain language (ISO 24495-1), microcopy (buttons, labels, empty states, onboarding, notifications), error and feedback message craft, and the accessibility and localization of interface

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download martinholovsky-sota-skills-skills_sota-ux-writing-582d6f9.zip · 18 KB
Part of martinholovsky/sota-skills — 39 skills

Install

skills CLI npx skills add https://github.com/martinholovsky/SOTA-skills/tree/main/skills/sota-ux-writing
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install martinholovsky-sota-skills@llmmart
Git git clone https://github.com/martinholovsky/SOTA-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole martinholovsky/sota-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

SOTA UX Writing & Product Copy

Purpose

Expert-level rules for the words inside the product: labels, buttons, errors, empty states, onboarding, notifications, and every other string a user reads while trying to get something done. The core thesis: interface text is functional infrastructure, not decoration — a user reads it under task pressure, often in a second language, often through a screen reader, and every word either moves them forward or costs them a support ticket.

Boundaries: this skill owns the language. The UX patterns the language lives in (when to show an empty state, validation timing, dialog escalation) are sota-frontend-design rules/04; technical documentation is sota-docs-workflow; outward-facing marketing content is sota-copywriting; CLI-specific output contracts are sota-cli-ux.

BUILD mode

When writing or changing interface text:

  1. Write the unhappy paths first — errors, empty states, and confirmations carry more consequence than the happy path (rules/03).
  2. Apply the plain-language spine to every string: relevant, findable, understandable, actionable (ISO 24495-1) — front-load the point, one idea per sentence, common words (rules/01).
  3. Name things once. Check the product glossary before introducing a term; never let the UI, docs, and API call the same concept different names (rules/01 §4).
  4. Buttons are verbs with objects ("Delete project", never "OK"/"Yes"), and every dialog states the specific consequence (rules/02).
  5. Write for the translator and the screen reader as you go: externalized strings, ICU plurals, named placeholders, descriptive link text, accessible names matching visible labels (rules/04).
  6. Before finishing, run each loaded rules file's Audit checklist against the diff — including the greps for banned strings.

AUDIT mode

When reviewing existing product copy:

  1. Inventory the string surface: localization files (*.json, *.po, *.strings, *.xlf), hardcoded literals in components, error-message constants, email/notification templates.
  2. Run the banned-pattern greps from each rules file's audit checklist (e.g. "click here", "an error occurred", "invalid input", concatenated plurals).
  3. Walk the critical flows as a user: signup, first-run empty state, a failed payment/submit, a destructive action. Judge every string against rules/01–03.
  4. Check localization readiness (rules/04) even for single-language products — retrofitting string externalization is expensive.

Severity conventions

  • Critical — copy that causes data loss or deception: destructive-action dialog whose buttons don't name the action; error message leaking secrets, internals, or account existence; consent text that misrepresents what happens; dark-pattern confirmshaming.
  • High — copy that blocks task completion or excludes users: error with no next step; placeholder used as the only label; icon-only control with no accessible name; untranslatable concatenated strings; link text meaningless out of context ("click here").
  • Medium — friction and inconsistency: same concept under two names; jargon or internal codenames in UI; blame-framed errors; toasts carrying the only path to an action; missing empty-state guidance.
  • Low — polish: tone drift, capitalization inconsistency, "please" inflation, exclamation marks, filler words ("simply", "just").

Finding format

file:line | rule violated | severity | effort | fix — quote the current string and propose the exact replacement string; copy findings without a proposed rewrite are half-findings.

Rules index

File Read this when...
rules/01-voice-tone-plain-language.md Establishing or auditing the overall language system: voice vs tone, stress-aware tone shifts, plain-language rules (ISO 24495-1), readability, terminology/glossary discipline, capitalization and punctuation conventions.
rules/02-microcopy-components.md Writing the words in components: buttons/CTAs, labels vs placeholders vs hints, empty states, onboarding and tooltips, confirmation dialogs, notifications/toasts/badges, counts and plurals, relative time, link text, loading/progress text.
rules/03-errors-feedback.md Anything that reports a problem or outcome: error-message anatomy, no-blame framing, validation wording, security-sensitive errors, warnings vs errors, success confirmations, permission prompts, and dark-pattern-free upsell moments.
rules/04-accessibility-localization.md Making the language reach everyone: WCAG 2.2 language criteria, alt text craft, accessible names, screen-reader-facing strings, string externalization, ICU plurals, translator context, text expansion, RTL, inclusive language.

Top-10 non-negotiables

  1. Every error says what happened and what to do next — a dead-end error is a support ticket with extra steps. (rules/03 §1)
  2. Buttons and dialog actions are verb+object ("Delete project", "Keep editing") — never bare "OK/Yes/No" pairs on anything consequential. (rules/02 §1, §5)
  3. The system takes the blame: "We couldn't save your changes", never "You entered invalid data". (rules/03 §2)
  4. One concept, one name, everywhere — UI, docs, API, support. Maintain a glossary; renames are migrations, not edits. (rules/01 §4)
  5. Placeholders are never labels; hint text never disappears while the user still needs it. (rules/02 §2)
  6. No copy relies on color, position, or an icon alone to carry meaning; every control has an accessible name matching its visible label. (rules/04 §2)
  7. Link and button text stands alone: "View invoice #1042", never "click here" or a bare "Learn more". (rules/02 §8)
  8. No string concatenation, ever — ICU MessageFormat with named placeholders and real plural rules; "1 items" ships nowhere. (rules/04 §4)
  9. Front-load every string: the first three words carry the point; scanning users read little else. (rules/01 §2)
  10. Security-sensitive copy never leaks: auth errors don't confirm account existence, error surfaces never show stack traces, internals, or secrets — cross-check sota-code-security rules/02 and /07. (rules/03 §4)
Files (sota-skills)
  • rules
    • 01-voice-tone-plain-language.md 7.3 KB
      # 01 — Voice, Tone & Plain Language
      
      The language system comes before any individual string. Voice is who the
      product sounds like (constant); tone is how that voice adapts to the moment
      (variable). Plain language is the floor under both.
      
      ## 1. Voice: one identity, written down
      
      - Define voice as **3–4 attributes, each with a "this, not that" pair**, and
        keep it in the repo next to the design tokens — a voice that lives in a
        slide deck governs nothing:
      
        ```text
        Confident, not boastful:   "Your backup is safe."  not  "Our world-class engine protected your backup!"
        Plain, not chummy:         "Couldn't connect."     not  "Whoopsie! The internet gremlins got us 🙈"
        Precise, not academic:     "Retries in 30 s."      not  "A retry will be attempted subsequent to a 30-second interval."
        ```
      
      - Voice applies to *every* string a user sees — validation messages, plan
        names, 404 pages — not just the marketing-adjacent surfaces.
      - Personality is seasoning, not structure: a joke may live in a success state
        or empty state; it never lives in an error that cost the user something.
      
      ## 2. Plain language: the ISO 24495-1 spine
      
      ISO 24495-1:2023 defines plain language by outcome — for the *reader*, the
      content must be **relevant, findable, understandable, and actionable**. Apply
      it per string:
      
      - **Relevant** — say only what the user needs *at this moment in the task*.
        Background, caveats, and internal reasoning go to docs, not dialogs.
      - **Findable** — front-load: the first three words carry the point. Users
        scan; put the operative word first ("Delete 14 files?" not "Are you sure
        you would like to proceed with deleting 14 files?").
      - **Understandable** — common words, one idea per sentence, active voice,
        present tense. Target roughly conversational reading level for
        general-audience products; readability scores (Flesch etc.) are directional
        tools, not gates — a developer tool may correctly use "idempotent".
      - **Actionable** — end at the action: what the user does next is explicit and
        usually *is* the button.
      
      Concrete rewrites:
      
      ```text
      BAD:  An error has occurred while processing your request. Please try again later.
      GOOD: We couldn't save your changes — the server didn't respond. Retry now, or copy your text first.
      
      BAD:  Authentication credentials were determined to be invalid.
      GOOD: Wrong email or password.
      
      BAD:  In order to be able to utilize the export functionality, it is necessary to first select a project.
      GOOD: Select a project to export.
      ```
      
      ## 3. Sentence mechanics
      
      - **Active voice, user or system as actor**: "We deleted the draft" /
        "You're offline", not "The draft has been deleted".
      - **One idea per sentence; ~15–20 words as a working ceiling** for UI prose
        (heuristic, not a rule to lawyer).
      - **No double negatives** ("Don't disable…"), no nested conditionals in one
        sentence — split or restructure as a list.
      - **Verbs over noun-stacks**: "when the export finishes" not "upon completion
        of the export process".
      - **Cut filler that presumes ease**: "simply", "just", "easily", "obviously"
        — if it were simple, the user wouldn't be reading a message about it.
      - **"Please" and "sorry" budgets**: please only when asking the user to do
        work for the system's benefit; sorry only when the product actually failed
        them — both lose meaning when sprayed on every string.
      - **Exclamation marks: at most in celebratory moments**, never in errors or
        instructions.
      
      ## 4. Terminology: one concept, one name
      
      - Maintain a **glossary as the single source of truth** — a tracked file, not
        tribal knowledge. Every user-visible noun for a product concept is in it.
      - **The UI, docs, API, CLI, and support macros use the same term.** "Workspace"
        in the UI + "org" in the API + "team" in the docs = three support tickets
        about the same object.
      - **Match the user's vocabulary, not the org chart's**: no internal codenames,
        service names, or team jargon in UI strings (also an information-disclosure
        smell — see `sota-code-security` rules/07).
      - **Renames are migrations**: update UI + docs + templates + support content
        in one release, with the old term searchable in help for a transition
        period. A half-renamed concept is worse than a badly-named one.
      - Reserved/loaded words get one meaning: "delete" (gone, maybe undoable) vs
        "remove" (taken out of this context, still exists) vs "archive" (hidden,
        recoverable) — pick the mapping once and never blur it.
      
      ## 5. Tone: adapt to the user's state, not your mood
      
      Tone shifts with the user's stress, in one direction — **the more stressed the
      user, the plainer and calmer the language**:
      
      | Moment | Tone | Example |
      |---|---|---|
      | Success, milestones | Warm, may be light | "Nice — your first deploy is live." |
      | Neutral tasks, settings | Plain, efficient | "Changes save automatically." |
      | Errors, blocked tasks | Calm, concrete, zero humor | "Payment failed — your card was declined. No charge was made." |
      | Data loss, security incidents | Sober, direct, no mascots | "We've signed you out everywhere as a precaution." |
      
      - Humor is opt-in and low-stakes only; a pun on a 500 page that ate a form
        submission reads as mockery.
      - Empty states and onboarding may carry the most personality; errors the
        least.
      
      ## 6. Capitalization, punctuation & mechanics — pick once, enforce
      
      - **Sentence case for everything** (buttons, titles, labels, menu items) is
        the 2026 default — it's faster to read, easier to keep consistent, and
        survives translation better than Title Case. Whichever you pick, encode it
        in the review checklist; mixed casing across surfaces is the most visible
        inconsistency a product ships.
      - Periods: full sentences in body text get them; fragments, labels, and
        single-sentence tooltips don't. Never on buttons.
      - Numerals for UI numbers ("3 files", not "three files"); locale-format
        numbers, dates, and currency programmatically (`rules/04` §5) — never
        hand-write "MM/DD/YYYY" into a string.
      - Contractions ("can't", "you're") are standard product voice; skip them only
        in legal/consent text where precision governs.
      - Ellipsis for in-progress ("Saving…", one character, not "..."), never for
        coyness.
      
      ## Audit checklist
      
      - [ ] A written voice definition with this-not-that pairs exists in the repo,
            and error strings actually follow it
      - [ ] Strings front-load the point — grep long openers:
            `grep -riE '"(In order to|Please note that|It is (necessary|recommended)|Are you sure you would like)' --include='*.json' --include='*.ts' --include='*.tsx'`
      - [ ] Active voice and present tense dominate; no double negatives in any
            user-facing string
      - [ ] Filler ban holds: `grep -riE '"(Simply|Just) ' locales/ src/` returns
            only justified hits
      - [ ] One name per concept: sample 10 core nouns; UI, docs, and API agree on
            all 10; glossary file exists and is current
      - [ ] No internal codenames/team jargon in UI strings
      - [ ] delete/remove/archive each mean exactly one thing across the product
      - [ ] Tone matrix respected: no humor or exclamation marks in error strings
            (`grep -riE '"[^"]*(Oops|Whoops|Uh.?oh)[^"]*"' src/ locales/`)
      - [ ] One casing convention (sentence case or Title Case) applied everywhere;
            buttons carry no trailing periods
      - [ ] Numbers, dates, and currency are locale-formatted in code, not
            hand-written into strings
      
    • 02-microcopy-components.md 7.9 KB
      # 02 — Microcopy: The Words in Components
      
      Component-level copy, mapped to the component patterns in
      `sota-frontend-design` rules/04 (which owns the interaction mechanics — this
      file owns what the strings say).
      
      ## 1. Buttons & action labels
      
      - **Verb first, object when consequential**: "Create project", "Send invoice",
        "Delete 14 files". A button is the answer to "what happens when I press
        this?" — if the label can't answer it, the label is wrong.
      - Generic labels are allowed only where the action is the whole screen's
        obvious subject ("Save" in a settings form); "Submit" is never right.
      - **Paired actions name both paths**: "Delete project / Keep project",
        "Discard changes / Keep editing" — never "Yes / No", "OK / Cancel" on
        anything destructive or ambiguous (which button is "cancel the cancel"?).
      - Buttons ≤ ~3 words; the sentence explaining the action belongs in the body
        text above it, not in the button.
      - Ellipsis convention: trailing "…" when the action opens a further step
        ("Export…" opens options; "Export" does it) — apply it consistently or not
        at all.
      
      ## 2. Labels, placeholders, hints
      
      - Every input has a **visible label naming the data** ("Work email"), not an
        instruction ("Enter your work email here") — the instruction form breaks
        scanning and translation reuse.
      - **Placeholder is never the label** (it vanishes on input — mechanics in
        frontend rules/04 §1). Use placeholders only for *format examples*
        ("name@company.com") and treat them as expendable.
      - Persistent requirements go in **hint text below the field**, present before
        the user types, not revealed as a scolding after failure: "8+ characters
        with a number" as a hint beats "Password too weak" as an error.
      - Label the unit, not just the number: "Timeout (seconds)", never a bare
        "Timeout" over an integer field.
      
      ## 3. Empty states: three different scripts
      
      Empty states differ by cause (pattern split in frontend rules/04 §5); the
      copy jobs differ too:
      
      - **First use** — orient + invite: what this area will hold, why it's
        valuable, one action to start. "No dashboards yet. Build one from a
        template, or start blank." Personality is welcome here.
      - **User-cleared** (search/filter → 0) — echo the query, offer the exit:
        "No results for 'invoce'. Check the spelling or clear filters." Never
        reuse first-use copy — "Create your first invoice!" atop a filtered list of
        200 invoices reads as data loss.
      - **Error-empty** — it's an error message (see `rules/03`), not an empty
        state: "Couldn't load projects — retry." Never imply the user has nothing
        when the truth is the fetch failed.
      
      ## 4. Onboarding, tooltips & in-product education
      
      - Onboarding copy earns each interruption: one concept per step, tied to the
        user's *current* goal, skippable, and it never re-explains what the UI
        already says. If the tour must explain a control, first ask why the
        control's own label failed.
      - **Tooltips are a last resort**, never a home for critical information —
        they're invisible on touch and to most screen-reader flows. Icon-only
        buttons get tooltips *and* accessible names (`rules/04` §2), but anything
        the user must know lives in visible text.
      - Progressive disclosure of concepts: name a concept when the user first
        meets it, not in a wall of definitions up front.
      - Kill stale education: a "New!" badge older than one release cycle, or a tip
        the user dismissed twice, is noise the product is wearing.
      
      ## 5. Confirmation dialogs
      
      The words are the safety mechanism (interaction rules in frontend rules/04 §6):
      
      ```text
      GOOD                                        BAD
      Title: Delete project "Acme"?               Title: Are you sure?
      Body: This deletes 14 deployments and       Body: This action cannot be undone.
            their logs. You can't undo this.
      Buttons: [Cancel]  [Delete project]         Buttons: [No]  [Yes]
      ```
      
      - Title = question naming the **specific object**; body = the **concrete
        consequence** (what, how much, reversible or not); confirm button repeats
        the verb.
      - "This action cannot be undone" alone is boilerplate that says nothing about
        *what* the action does — state the consequence, then the irreversibility.
      - Type-to-confirm prompts name what to type and why the friction exists.
      
      ## 6. Notifications, toasts & badges
      
      - A toast is **one sentence, outcome first, optional single action**:
        "Invoice sent — Undo". No stacked clauses, no second sentence; if it needs
        more, it needs a different surface (toast mechanics: frontend rules/04 §9).
      - Notifications (push/in-app) must stand alone on a lock screen: actor +
        action + object ("Ana commented on Q3 budget"), no "You have a new
        notification" (that's the notification's job description, not its content).
        Interruption budget and delivery: `sota-mobile` rules/03.
      - Badges/counts: a number means *actionable items*, not "stuff exists" —
        a badge that never reaches zero trains users to ignore it.
      
      ## 7. Counts, plurals, time & truncation
      
      - **Plurals via ICU MessageFormat, never string math** — "1 items" is the
        canonical i18n failure (full rules in `rules/04` §4):
        `{count, plural, one {# item} other {# items}}`.
      - Zero is usually a sentence, not a number: "No results" beats "0 results";
        exceptions are dense dashboards where the column shape matters.
      - Relative time for the recent past ("just now", "4 min ago"), switching to
        absolute dates beyond ~a week; always expose the exact timestamp (tooltip
        or `<time title>`), because "3 weeks ago" is useless in an audit trail.
      - Truncate middles, not ends, when the distinguishing part is the tail
        ("inv…-2026-041.pdf"); never truncate away the only difference between two
        listed items.
      
      ## 8. Link text & inline actions
      
      - Link text describes the destination and survives out of context (WCAG 2.4.4
        — screen-reader users navigate by link list): "View billing settings",
        never "click here", "here", or a bare "Learn more".
      - "Learn more" is acceptable only with an accessible extension
        ("Learn more about roles" via `aria-label` or visible text).
      - Don't hyperlink vague nouns mid-sentence ("There was a problem") — link the
        action ("Retry the sync").
      
      ## 9. Loading & progress text
      
      - Name the work, not the wait: "Importing 3 of 12 contacts…" beats
        "Loading…" beats a bare spinner (thresholds in frontend rules/04 §4).
      - Long operations set expectations honestly ("This usually takes ~2 min");
        never promise "a few seconds" unmeasured.
      - Post-completion, say what changed: "Imported 12 contacts (2 duplicates
        skipped)" — the skipped count prevents the "where are my other rows?"
        ticket.
      
      ## Audit checklist
      
      - [ ] No "Submit", "OK/Yes/No" pairs, or bare "Cancel/Confirm" on
            consequential dialogs:
            `grep -riE '"(Submit|OK|Yes|No)"' locales/ src/ --include='*.json'`
      - [ ] Destructive buttons repeat the verb and object; dialog bodies state the
            concrete consequence, not just "cannot be undone"
      - [ ] Every input: visible noun label; placeholders are format examples only;
            requirements shown as hints before first failure
      - [ ] Empty states: three variants (first-use / filtered / error) with
            distinct copy; filtered-zero echoes the query; error-empty never poses
            as "you have nothing"
      - [ ] Tooltips carry no critical-path information; no "New!" badges older
            than a release cycle
      - [ ] Toasts are single-sentence, outcome-first; notifications name
            actor+action+object and stand alone out of app context
      - [ ] No concatenated plurals:
            `grep -rnE '\+\s*["'\''](item|file|result|user)s?["'\'']' src/` is clean;
            ICU plural forms used for every count
      - [ ] Relative timestamps switch to absolute past ~1 week and expose exact
            time; truncation never hides the distinguishing segment
      - [ ] Link text stands alone: `grep -riE '>(click here|here|learn more)<' src/`
            returns nothing unlabeled
      - [ ] Loading strings name the operation; completion strings report what
            changed, including skips/failures
      
    • 03-errors-feedback.md 7.6 KB
      # 03 — Errors, Warnings & Feedback
      
      Error copy is the highest-stakes writing in the product: the user is blocked,
      stressed, and deciding whether to retry, rage-quit, or file a ticket. Every
      error message is a tiny support agent — or a tiny liability.
      
      ## 1. The error contract: what happened → why → what now
      
      Every user-facing error answers, in order, as much of this as is truthfully
      known:
      
      1. **What happened** — specific, in task terms: "Payment failed",
         "Couldn't upload sketch.png".
      2. **Why** (when known and safe to say): "your card was declined",
         "the file is larger than 25 MB".
      3. **What to do next** — an action, ideally a button: "Try another card",
         "Compress it or upgrade for 1 GB uploads".
      4. **State the side effects**: after a failed payment, "No charge was made"
         is the sentence the user is actually looking for.
      
      ```text
      BAD:  Error 4002: Transaction could not be processed.
      GOOD: Payment failed — your card was declined by the bank. No charge was
            made. Try another card, or contact your bank and retry.
      ```
      
      - A message that can't offer a next step isn't done: "Retry", "Contact
        support with code 4002", or "We're on it — check status.example.com" are
        all next steps. Dead ends are findings, always.
      - Error **codes are for support, in fine print** — never the headline. Keep
        them (they make tickets resolvable); demote them.
      - Never say "try again later" when retrying cannot help (validation errors,
        permissions) — it's a lie that costs the user a second failure.
      
      ## 2. Framing: the system takes the blame
      
      - The product failed the user, not the reverse: "We couldn't save your
        changes", never "You entered invalid data". Grammatical subject = system.
      - Banned framing: "Invalid input", "Bad request", "Illegal characters",
        "You failed to…", "Forbidden" as user-facing text — these are protocol
        vocabulary leaking into human space.
      - No alarm styling in prose: no ALL CAPS, no exclamation marks, no red walls
        of text. The visual layer already signals severity.
      - Humor: **never in errors.** "Oops!"/"Whoops!" atop lost work reads as
        mockery; the calmer the copy, the more competent the product feels
        (tone matrix: `rules/01` §5).
      - Apologize once, when the product genuinely failed ("Sorry — this is on
        us"), not reflexively on every validation message.
      
      ## 3. Validation messages: state the rule, not the verdict
      
      - The message teaches the constraint: "Use 8+ characters with at least one
        number", not "Password too weak" / "Invalid password".
      - Include the offending value when it helps: "Card number must be 16 digits —
        you entered 15."
      - Requirements the user could have known belong in hint text *before* the
        error (`rules/02` §2); validation copy is the fallback, not the reveal.
      - Match the field's language: the error under "Work email" says "work email",
        not "identifier".
      - Timing/mechanics (blur vs keystroke, focus management, `aria-describedby`)
        are `sota-frontend-design` rules/04 §3 — copy and mechanics fail together.
      
      ## 4. Security-sensitive errors: precise inside, vague outside
      
      Copy here is a security control — coordinate with `sota-code-security`
      rules/02 (authn) and rules/07 (data exposure):
      
      - **Login failures never confirm account existence**: "Wrong email or
        password", never "No account with that email" (user-enumeration). The same
        applies to password-reset flows ("If that address has an account, we've
        emailed a link").
      - Rate-limit and lockout messages state the fact and the wait, not the
        detection logic: "Too many attempts — try again in 15 minutes."
      - **No internals ever reach the user**: stack traces, SQL fragments, file
        paths, hostnames, dependency names, "NullPointerException". The user-facing
        string and the logged diagnostic are two different strings by design.
      - Permission denials name the *rule*, not the resource's existence when the
        user shouldn't know it exists: prefer 404-equivalent copy for unauthorized
        IDs (IDOR hygiene).
      - Session expiry: say why re-auth is needed and preserve their work — copy
        and mechanics together ("Your session expired. Sign in again — your draft
        is saved.").
      
      ## 5. Warnings vs errors vs info: spend alarm carefully
      
      - **Error** = the task failed or will fail. **Warning** = it will proceed
        but with a consequence worth weighing. **Info** = context, no decision.
        Escalating info to warning (or warning to error) to "make sure they read
        it" trains users to dismiss all three — alarm fatigue is a copy bug
        (`sota-detection-engineering` has the ops version of this law).
      - A warning states the consequence and the choice: "This plan change takes
        effect immediately and is prorated. Continue?"
      - Persistent warnings the user can't act on ("Your browser may be
        unsupported") are banned — either gate, fix, or stay silent.
      
      ## 6. Success & completion feedback
      
      - Confirm the **object and the effect**, not the click: "Invoice #1042 sent
        to billing@acme.com", not "Success!".
      - Include the reversal path in the same breath when one exists: "Archived —
        Undo" (undo-over-confirm pattern: frontend rules/04 §6).
      - Report partial success honestly: "Imported 200 of 203 rows — 3 failed
        (download report)". Rounding partial failure up to "Done!" is a trust
        bug that surfaces as "the product loses data".
      - Skip ceremony for micro-actions: a toggle that visibly flips needs no
        toast; feedback is proportional to consequence.
      
      ## 7. Asking for things: permissions, upsells, reviews
      
      - Permission prompts (notifications, location, contacts) state the **user's
        benefit and the trigger**, and ask in context — "Get an alert when your
        build finishes" beside the build button, never a cold launch-time battery
        of dialogs (platform specifics: `sota-mobile` rules/03).
      - Upgrade/paywall moments: name what the user just hit ("You've used all 3
        free projects"), what the paid tier changes, and keep the decline path
        neutral — **"Not now" / "No thanks", never confirmshaming** ("No thanks, I
        like losing data"). Confirmshaming is a dark pattern (regulatory context:
        `sota-copywriting` rules/04 §4) and a Critical finding here.
      - Never gate the *decline* behind lower contrast, tiny type, or a delay —
        visual-hierarchy manipulation of consent is the same dark pattern in CSS.
      - Review/feedback prompts: after a success moment, once, with a real
        dismiss-forever option.
      
      ## Audit checklist
      
      - [ ] Every user-facing error has all applicable parts: what happened, why
            (if known), next step, side effects; zero dead ends
      - [ ] Generic-error ban: `grep -riE '"(An error (has )?occurred|Something went
            wrong)"' src/ locales/` — each hit either enriched or justified
      - [ ] System-blame framing: `grep -riE '"(Invalid|Illegal|Bad|Forbidden)[^"]*"'
            locales/` — no protocol vocabulary or user-blame in UI strings
      - [ ] No humor/exclamations in errors:
            `grep -riE '"[^"]*(Oops|Whoops|!)[^"]*"' locales/` reviewed
      - [ ] Validation messages state the rule and (where helpful) the offending
            value; constraints also appear as hints before first failure
      - [ ] Login/reset/lockout copy confirms no account existence; user-facing
            error strings contain no stack traces, paths, hostnames, or exception
            names (pair with `sota-code-security` rules/07 greps)
      - [ ] "Try again later" appears only where retry can actually succeed
      - [ ] Severity honest: no info styled/worded as warning, no warning as error;
            no unactionable persistent warnings
      - [ ] Success messages name object + effect; partial failures reported with
            counts and a path to the details
      - [ ] Permission/upsell prompts state user benefit in context; decline
            options are neutral ("Not now") — grep for confirmshaming patterns:
            `grep -riE '"No thanks, I' locales/ src/`
      
    • 04-accessibility-localization.md 7.7 KB
      # 04 — Accessible & Localizable Language
      
      The same string must work read aloud by a screen reader, translated into a
      language 35% longer, and rendered right-to-left. These are properties of how
      the string is *written and stored*, decided at authoring time — retrofitting
      is a rewrite.
      
      ## 1. WCAG 2.2 language criteria (the copy-owned subset)
      
      The full accessibility pass is `sota-frontend-design` rules/05; these criteria
      are satisfied or violated *by the words themselves*:
      
      - **3.1.1 / 3.1.2 (A/AA)** — page language declared (`<html lang>`), and
        inline foreign-language passages marked (`lang` on the element), or screen
        readers pronounce French with English phonemes.
      - **2.4.6 (AA)** — headings and labels describe topic or purpose: "Payment
        method", not "More details".
      - **2.4.4 (A)** — link purpose clear from the link text (rules/02 §8).
      - **3.3.2 (A)** — labels or instructions exist wherever input is required —
        the *presence* of the words is the criterion.
      - **3.1.5 (AAA)** — reading level: not required at AA, but treat
        plain-language discipline (`rules/01` §2) as how general-audience products
        approach it.
      
      ## 2. Accessible names: what the screen reader actually says
      
      - **Every interactive element has an accessible name**; icon-only buttons get
        `aria-label` — an unlabeled ✕ button is announced as "button", which is a
        locked door.
      - **Label-in-name (WCAG 2.5.3)**: the accessible name *contains* the visible
        label — voice-control users say what they see; `aria-label="Close dialog"`
        on a button visibly labeled "Dismiss" breaks "click Dismiss".
      - Accessible names are nouns/verb-phrases, not instructions: "Search", not
        "Click here to search".
      - Repeated card/row actions get differentiated names: every "Delete" in a
        list announces its object ("Delete invoice #1042") via `aria-label` or
        visually-hidden text — a link list of ten identical "Delete"s is unusable.
      - Status updates that appear visually get announced textually (`role=status`
        regions) with self-sufficient wording: "Saving… Saved" — mechanics in
        frontend rules/04, the *string* is owned here.
      
      ## 3. Writing for the ear and the eye
      
      - **Don't encode meaning in symbols alone**: "→", "✓", "❌", and emoji are
        read unpredictably or skipped; pair with words ("Done ✓" not bare "✓").
        Emoji never *replace* a word mid-sentence.
      - Avoid ASCII art, decorative unicode ("𝓯𝓪𝓷𝓬𝔂" fonts — screen readers spell
        them letter-by-letter or skip them), and meaning-bearing whitespace.
      - Abbreviations: expand on first use per surface, or don't abbreviate;
        screen readers guess ("approx." vs "APR" vs "apr").
      - Alt text: **function over appearance** ("Search" for a magnifier icon-link,
        not "magnifying glass"); decorative images get `alt=""`; informational
        charts get a text summary of the takeaway, not "chart". No "image of" /
        "picture of" prefixes — the role is already announced.
      
      ## 4. Localization-safe strings
      
      The engineering contract that makes translation possible:
      
      - **Every user-visible string is externalized** — no literals in components.
        New-string review happens in the locale file diff, which is also where the
        audit greps run.
      - **Never concatenate translated fragments** — word order differs by
        language; `"You have " + n + " items"` cannot be translated into German,
        Arabic, or Czech. One string per sentence, full stop.
      - **ICU MessageFormat** for anything variable: named placeholders
        (`{count}`, `{fileName}` — never positional `{0}`), plural rules
        (`{count, plural, one {…} few {…} other {…}}` — Slavic languages have 3–4
        forms; English's one/other is the simple case), select for gender where the
        language needs it.
      - **Translator context ships with the string**: a description field/comment
        saying where it appears and what the placeholders are ("Button on the
        billing page; {date} is the next charge date"). "Book" without context is
        untranslatable (noun? verb?).
      - **Don't reuse a string across meanings**: "Archive" the button and
        "Archive" the section title may translate differently — one key per
        usage-meaning, even if English happens to coincide.
      - No linguistic logic in code: no `word + "s"`, no `"a " + noun`
        (a/an breaks), no capitalizing via `toUpperCase()` on the first letter of a
        translated string (locale-sensitive — Turkish dotless-ı is the classic
        corruption).
      
      ## 5. Layout-facing and locale-facing consequences
      
      - **Budget for expansion**: German/Finnish run roughly +35% over English
        (worst-case strings and container behavior: `sota-frontend-design` rules/04 §2a); write
        short English *and* let containers grow — both, not either.
      - Dates, numbers, currency, lists, relative time: **always through locale
        APIs** (`Intl.*` on the web, platform equivalents elsewhere) — never
        string-built. Currency symbols placement, decimal commas, and week starts
        are all locale data, not copy.
      - RTL: user-generated content gets `dir="auto"`; avoid strings that assume
        left/right ("see the panel on the right" → name the panel).
      - Idioms, sports/culture metaphors, humor, and wordplay don't travel —
        "home run", "back to square one", puns in empty states all become
        translator tickets. Plain language (`rules/01`) is the pre-translation.
      - Images with embedded text are banned (untranslatable, unindexable,
        inaccessible); text renders as text.
      
      ## 6. Inclusive language
      
      - Address the user as "you"; refer to unspecified people with singular
        "they" — never "he" as default or the clunky "he/she".
      - No ableist casualisms in UI or docs: "sanity check" → "consistency check",
        "crazy/insane" → "unexpected/extreme", "blind spot" (metaphorical) →
        "gap".
      - Industry-standard renames apply to user-visible text: allowlist/blocklist
        (not whitelist/blacklist), primary/replica (not master/slave) — align with
        the terms your APIs already migrated to.
      - Name people's attributes only when relevant to the task, using the
        product-glossary term; forms asking for personal attributes follow
        `sota-privacy-compliance` minimization first (don't write copy for a field
        that shouldn't exist).
      - No culture-bound assumptions in examples: names, holidays, address and
        family structures vary — example data is diverse or neutral.
      
      ## Audit checklist
      
      - [ ] `<html lang>` set and correct; inline foreign phrases carry `lang`;
            pages with switchable UI language update it dynamically
      - [ ] Every icon-only control has an accessible name; names contain the
            visible label (2.5.3); repeated row actions announce their object —
            `grep -rn 'aria-label' src/ | grep -iE 'click|here'` is empty
      - [ ] Headings/labels descriptive (2.4.6); required-input instructions
            present (3.3.2)
      - [ ] No meaning carried by symbol/emoji alone; alt text is functional, empty
            for decorative, and never starts with "image of":
            `grep -riE 'alt="(image|picture|photo) of' src/`
      - [ ] Zero hardcoded UI strings in components (spot-check by adding a
            pseudo-locale and hunting untranslated text)
      - [ ] No string concatenation or linguistic code:
            `grep -rnE '\+\s*["'\''](s|es)["'\'']|["'\'']\s*\+\s*(count|n|num)' src/`
            and no `toUpperCase()`/`capitalize` on translated strings
      - [ ] ICU plurals with named placeholders everywhere counts appear; no
            positional `{0}` placeholders
      - [ ] Translator context present for ambiguous strings; no key reused across
            different meanings
      - [ ] All dates/numbers/currency via `Intl.*`/platform locale APIs:
            `grep -rnE '(MM/DD|DD/MM|toFixed\(2\).*[$€£])' src/` reviewed
      - [ ] `dir="auto"` on user-generated text containers; no "left/right"
            directional language in strings; no text embedded in images
      - [ ] Inclusive-language sweep:
            `grep -riE '(whitelist|blacklist|sanity check|master/slave)' src/ locales/ docs/`
            returns nothing user-visible
      
  • SKILL.md 7.2 KB
    ---
    name: sota-ux-writing
    description: >-
      State-of-the-art UX writing and product-copy guidance (2026) covering voice
      and tone systems, plain language (ISO 24495-1), microcopy (buttons, labels,
      empty states, onboarding, notifications), error and feedback message craft,
      and the accessibility and localization of interface language (WCAG 2.2
      language criteria, alt text, ICU pluralization, i18n-safe strings). Use when
      writing or reviewing ANY user-facing interface text — web, mobile, desktop,
      or CLI products — AND when auditing existing product copy for clarity,
      consistency, tone, accessibility, and localization readiness. Trigger
      keywords: UX writing, microcopy, product copy, content design, error message,
      empty state, button label, tooltip, onboarding copy, form label, placeholder,
      notification, toast, confirmation dialog, tone of voice, terminology, plain
      language, readability, alt text, i18n strings, localization, translation.
    ---
    
    # SOTA UX Writing & Product Copy
    
    ## Purpose
    
    Expert-level rules for the *words inside the product*: labels, buttons, errors,
    empty states, onboarding, notifications, and every other string a user reads
    while trying to get something done. The core thesis: **interface text is
    functional infrastructure, not decoration — a user reads it under task
    pressure, often in a second language, often through a screen reader, and every
    word either moves them forward or costs them a support ticket.**
    
    Boundaries: this skill owns the *language*. The UX *patterns* the language
    lives in (when to show an empty state, validation timing, dialog escalation)
    are `sota-frontend-design` rules/04; technical documentation is
    `sota-docs-workflow`; outward-facing marketing content is `sota-copywriting`;
    CLI-specific output contracts are `sota-cli-ux`.
    
    ## BUILD mode
    
    When writing or changing interface text:
    
    1. **Write the unhappy paths first** — errors, empty states, and confirmations
       carry more consequence than the happy path (`rules/03`).
    2. **Apply the plain-language spine** to every string: relevant, findable,
       understandable, actionable (ISO 24495-1) — front-load the point, one idea
       per sentence, common words (`rules/01`).
    3. **Name things once.** Check the product glossary before introducing a term;
       never let the UI, docs, and API call the same concept different names
       (`rules/01` §4).
    4. **Buttons are verbs with objects** ("Delete project", never "OK"/"Yes"),
       and every dialog states the specific consequence (`rules/02`).
    5. **Write for the translator and the screen reader as you go**: externalized
       strings, ICU plurals, named placeholders, descriptive link text, accessible
       names matching visible labels (`rules/04`).
    6. Before finishing, run each loaded rules file's **Audit checklist** against
       the diff — including the greps for banned strings.
    
    ## AUDIT mode
    
    When reviewing existing product copy:
    
    1. Inventory the string surface: localization files (`*.json`, `*.po`,
       `*.strings`, `*.xlf`), hardcoded literals in components, error-message
       constants, email/notification templates.
    2. Run the banned-pattern greps from each rules file's audit checklist
       (e.g. `"click here"`, `"an error occurred"`, `"invalid input"`,
       concatenated plurals).
    3. Walk the critical flows as a user: signup, first-run empty state, a failed
       payment/submit, a destructive action. Judge every string against
       `rules/01`–`03`.
    4. Check localization readiness (`rules/04`) even for single-language
       products — retrofitting string externalization is expensive.
    
    ### Severity conventions
    
    - **Critical** — copy that causes data loss or deception: destructive-action
      dialog whose buttons don't name the action; error message leaking secrets,
      internals, or account existence; consent text that misrepresents what
      happens; dark-pattern confirmshaming.
    - **High** — copy that blocks task completion or excludes users: error with no
      next step; placeholder used as the only label; icon-only control with no
      accessible name; untranslatable concatenated strings; link text meaningless
      out of context ("click here").
    - **Medium** — friction and inconsistency: same concept under two names;
      jargon or internal codenames in UI; blame-framed errors; toasts carrying the
      only path to an action; missing empty-state guidance.
    - **Low** — polish: tone drift, capitalization inconsistency, "please"
      inflation, exclamation marks, filler words ("simply", "just").
    
    ### Finding format
    
    `file:line | rule violated | severity | effort | fix` — quote the current
    string and propose the exact replacement string; copy findings without a
    proposed rewrite are half-findings.
    
    ## Rules index
    
    | File | Read this when... |
    |---|---|
    | `rules/01-voice-tone-plain-language.md` | Establishing or auditing the overall language system: voice vs tone, stress-aware tone shifts, plain-language rules (ISO 24495-1), readability, terminology/glossary discipline, capitalization and punctuation conventions. |
    | `rules/02-microcopy-components.md` | Writing the words in components: buttons/CTAs, labels vs placeholders vs hints, empty states, onboarding and tooltips, confirmation dialogs, notifications/toasts/badges, counts and plurals, relative time, link text, loading/progress text. |
    | `rules/03-errors-feedback.md` | Anything that reports a problem or outcome: error-message anatomy, no-blame framing, validation wording, security-sensitive errors, warnings vs errors, success confirmations, permission prompts, and dark-pattern-free upsell moments. |
    | `rules/04-accessibility-localization.md` | Making the language reach everyone: WCAG 2.2 language criteria, alt text craft, accessible names, screen-reader-facing strings, string externalization, ICU plurals, translator context, text expansion, RTL, inclusive language. |
    
    ## Top-10 non-negotiables
    
    1. **Every error says what happened and what to do next** — a dead-end error
       is a support ticket with extra steps. (rules/03 §1)
    2. **Buttons and dialog actions are verb+object** ("Delete project",
       "Keep editing") — never bare "OK/Yes/No" pairs on anything consequential.
       (rules/02 §1, §5)
    3. **The system takes the blame**: "We couldn't save your changes", never
       "You entered invalid data". (rules/03 §2)
    4. **One concept, one name**, everywhere — UI, docs, API, support. Maintain a
       glossary; renames are migrations, not edits. (rules/01 §4)
    5. **Placeholders are never labels**; hint text never disappears while the
       user still needs it. (rules/02 §2)
    6. **No copy relies on color, position, or an icon alone** to carry meaning;
       every control has an accessible name matching its visible label.
       (rules/04 §2)
    7. **Link and button text stands alone**: "View invoice #1042", never
       "click here" or a bare "Learn more". (rules/02 §8)
    8. **No string concatenation, ever** — ICU MessageFormat with named
       placeholders and real plural rules; "1 items" ships nowhere. (rules/04 §4)
    9. **Front-load every string**: the first three words carry the point;
       scanning users read little else. (rules/01 §2)
    10. **Security-sensitive copy never leaks**: auth errors don't confirm account
        existence, error surfaces never show stack traces, internals, or secrets
        — cross-check `sota-code-security` rules/02 and /07. (rules/03 §4)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related