Skip to content

07 - Voice & Copywriting

Microcopy is code. A bad button label costs more than a bad variable name.

This chapter codifies every string the user sees. Follow Ant’s copy chapter (ant.design/docs/spec/copywriting) closely, layer Linear’s plain-language ethos on top.

Rule of thumb: say what the user can do, not what the system can do. “Send invitation” beats “Invitation will be sent”.


Tone by surface

Same tokens, different voice. Keep them distinct on purpose.

SurfaceToneExample
AdminConcise, direct, technical”Save changes”, “Export failed: quota exceeded”
Chat (end-user)Warm, conversational”Hi — what would you like to know?”
Error / exceptionObjective, actionable”Unable to save. Name must be unique.”
SuccessCalm, brief”Saved”
Empty stateInviting, specific”No agents yet. Create your first one to get started.”
Onboarding tooltipHelpful, light”Tip: press ⌘K to search”

Admin is a tool. Chat is a conversation partner. Don’t confuse them.


Global copy rules

Punctuation

  • Labels, buttons, titles, tooltips, table headers, menu items: no trailing period.
  • Sentences in body text / descriptions: period is fine.
  • Exclamation marks: greetings (“Welcome!”) and congratulations (“Import complete!”) only. Never in errors, never in neutral text.
  • Em dashes: avoid. Use commas, periods, or ”…”.
  • Curly quotes: use straight quotes in UI strings (simpler to diff/translate).

Capitalization

  • Sentence case for everything — labels, buttons, titles, table headers, empty-state messages.
  • Title Case only for product names (“Nexus Agentic”, “Nexus Admin”) and proper nouns.
  • Never ALL CAPS for emphasis. Use font-semibold if you need emphasis.

Sentence case examples:

  • “Create agent” ✓
  • “Create Agent” ✗
  • “CREATE AGENT” ✗

Numbers

Always Arabic numerals. 1,234 not “one thousand two hundred thirty-four”. Transmit faster, translate cleanly.

  • Dates → see 05-patterns.md → Data Format.
  • Counts in labels: “3 items” not “Three items”.
  • Range: “1-10” with hyphen, not “1 to 10”.
  • File sizes: binary units with a space before the suffix — 12 KB, 3.4 MB, 1.2 GB. Round to one decimal from MB and up; integers under 1 MB. Use the formatBytes helper in @na/utils, not Intl.NumberFormat (it doesn’t cover binary sizes).

Keyboard shortcuts — display format

Shortcut hints appear inside tooltips, menu items, and <kbd>-styled inline labels. The format adapts to the user’s platform:

KeymacOSWindows / Linux
CommandCtrl
OptionAlt
ShiftShift
EnterEnter
DeleteBackspace

Display examples: ⌘K on macOS, Ctrl+K on others. Combine with + (no space) on non-Mac. Detect via navigator.platform.startsWith('Mac') in a useIsMac() hook; never ship a single string for both platforms. For chord shortcuts, join with space: g t (go to tools). Always wrap in <kbd> for semantic meaning; style defaults to font-mono text-xs border px-1 rounded.

<code> — when raw mono style is allowed

Monospace font appears in three cases only:

  1. Identifiers: agent IDs, doc IDs, tool names (send_email), env var names.
  2. File paths and URLs: /api/v1/agents, ~/.gstack/projects.
  3. Code snippets in help text, onboarding, or error messages.

Never use mono for user-authored content (agent names, document titles) — that’s a typography mismatch that looks like a bug. Wrap in <code> (or font-mono text-sm) and keep it inline; multi-line code uses a <pre> block.

Vocabulary: pick one name per concept (C3)

Canonical names, with rejected synonyms. Enforced in UI strings, identifiers, docs.

CanonicalUse forRejected synonyms
AgentThe AI configuration (admin side)Bot, Assistant (user-facing in chat only — see below), Persona, Model
AssistantEnd-user-facing name for an Agent (chat UI label only)Bot, Companion, Buddy
ToolA function an agent can invokeSkill, Plugin, Extension, Action
Knowledge baseCollection of documents for RAGKB, Wiki, Corpus, Doc store
ThreadLangGraph-side conversation sessionSession, Chat
ConversationApp-level record (User + Agent + Thread)Chat, Dialog, Session
WorkflowAgent’s graph of nodes (1:1 with agent)Graph, Flow, Pipeline, Process
ChannelExternal integration (Slack, Zalo, web)Integration, Connector
WidgetEmbeddable chat UI for external sitesEmbed, iframe, Popup
VariableNamed input binding for agent configurationParam, Parameter, Context item
API keyCredential for programmatic accessToken, Secret (except when we mean the JWT)
MemberHuman user of the admin appUser (except for low-level tech contexts)
RolePermission groupingGroup, Access level

When you must use a rejected synonym (external API name, legacy URL), leave a comment explaining why. Never let the rejected term spread into new code.

Authoritative source: .claude/rules/agent-terminology.md.

Product names

NameWhen
Nexus AgenticProduct umbrella
Nexus AdminAdmin app (internal use only; users just see “Admin”)
Nexus ChatChat app (end-user sees “Chat”)

Title Case for these. Never hyphenated. Never all-caps.


Button labels — the most important strings

Structure

Verb + noun. Specific. Matches what happens.

GoodBad
”Save""OK"
"Save changes""Submit"
"Create agent""Create” (when in multi-entity context)
“Delete agent""Delete” (destructive = be explicit)
“Send invitation""Send"
"Add tool""New"
"Cancel""No” (confusing in a dialog)
“Discard changes""Yes” (confusing in a dialog)
“Sign in""Login” (one word, not a verb)
“Sign out""Log out” (inconsistent with “Sign in”)

Benefit-labeled CTAs (Ant)

When a button can be named by the user benefit, use that.

  • “Start free trial” > “Submit”
  • “Get my report” > “Generate”
  • “Save and keep editing” > “Save”

No-op labels to avoid

  • “OK”, “Confirm”, “Submit”, “Go”, “Apply” (rare) — all too generic.
  • “Click here” — inline link labels should describe the destination.
  • “More” — say what more of.

Destructive button copy

Always the verb. Always explicit. Never “OK” or “Confirm” in a delete dialog.

<ConfirmDialog
confirmLabel="Delete agent" // ✓ verb + noun
variant="destructive"
...
/>
// NOT:
confirmLabel="OK" // ✗
confirmLabel="Confirm" // ✗
confirmLabel="Delete" // ok, but "Delete agent" is clearer
confirmLabel="Yes" // ✗

Cancel vs discard vs close

  • Cancel — undo a pending action (dialog cancel, form cancel, cancel a mutation).
  • Discard changes — throw away unsaved edits (unsaved-changes dialog).
  • Close — dismiss a non-destructive panel (detail pane, info dialog).
  • Back — go up a level (back to list).

Form labels

Sentence case, no colons, no periods

<Label>Display name</Label> // ✓
<Label>Display Name:</Label> // ✗ (Title case + colon)
<Label>Display name.</Label> // ✗ (period)

Required marker

One asterisk suffix. aria-required="true" on the input.

<Label>Email <span aria-hidden="true">*</span></Label>
<Input aria-required="true" />

Optional marker

Don’t mark optional fields if most are required. If most are optional, mark the required ones. Never mark both.

Help text

Use <p className="text-muted-foreground text-xs"> below the input. Always visible.

  • Good: “Shown on your public profile.”
  • Bad: <Input placeholder="This is shown on your profile..."> (placeholder disappears)

Error messages

Objective. Actionable. No blame. Error text is the point where voice most often fails.

Template

<What went wrong>. <What to do next>.

BadGood
”Error: 500 Internal Server Error""Unable to save. Try again in a moment."
"You entered a bad value""Email must be a valid address"
"Failed to load agents""Couldn’t load agents. Check your connection and try again."
"Something went wrong""Upload failed. File must be under 5 MB."
"Operation not permitted""You don’t have permission to delete this agent."
"Name cannot be empty""Name is required"
"Not found""Agent not found. It may have been deleted.”

Don’t blame the user

  • “You entered…” → “The input must be…”
  • “Your file is too big” → “File must be under 5 MB”
  • “Incorrect password” → “Email or password is incorrect” (don’t leak which one)

Don’t expose stack traces

Strip server stack traces before display. If the user needs to contact support, show an opaque ID they can share, not the raw error.

// extractErrorMessage() in @na/utils parses ApiError JSON and returns the user-safe message.

Don’t apologize excessively

  • “We’re sorry, something went wrong” → “Something went wrong. Try again.”
  • Sorry is fine once per error, not three times per line.

Empty state copy

Four parts: title (fact), description (what to do next), primary action (the verb), optional secondary (alternative path).

PartRuleExample
TitleState the fact, don’t dramatize”No agents yet”
DescriptionExplain the value + next step, not what’s missing”Create your first agent to get started.”
Primary CTAVerb + noun”Create agent”
SecondaryOptional alternative”Import from template”

Bad:

  • Title: ”😔 It’s empty here!” (don’t dramatize)
  • Title: “0 Agents” (useless)
  • Description: “You haven’t created any agents” (blame + stating the obvious)
  • CTA: “Get started” (vague)

Good:

  • Title: “No agents yet”
  • Description: “Agents are the AI assistants you connect to your business data.”
  • CTA: “Create agent”

Empty state variants (copy)

VariantTitleDescription
Blank slate”No agents yet""Agents are AI assistants connected to your data. Create your first one.”
Filtered-to-zero”No agents match""Try adjusting your search or filters. [Clear filters]“
Permission-blocked”You don’t have access""Ask your admin to grant Agent Manager to your account.”
Network / error”Couldn’t load agents""Check your connection and try again. [Retry]“

Confirmation dialogs

Title

A question that states the consequence.

BadGood
”Are you sure?""Delete this agent?"
"Please confirm""Sign out?"
"Warning""Remove this tool from the agent?”

Description

Explain the consequence. What changes, what’s lost, whether it’s reversible.

Example
”This permanently deletes the agent, all threads, and all associated data. This cannot be undone."
"The tool will stop responding until you add it back."
"Any unsaved changes will be discarded.”

Buttons

[Cancel] + action verb. Never [Cancel][OK].

[Cancel] [Delete agent]
[Cancel] [Sign out]
[Keep editing] [Discard changes]

Success messages

Short. Stop dancing. Users already know they saved.

BadGood
”Congratulations! Saved!""Saved"
"Successfully deleted!""Deleted"
"Operation completed.”(ActionButton success tick is enough)

Loading copy

ContextCopy
Mutation pendingpendingText="Saving…"
Background job"Importing 234 of 1,000 rows"
Initial pageSkeleton (no copy)
Large file"Uploading your file…" + progress

Use the single ellipsis character () not three periods (...). It’s one character and one character’s worth of spacing.


Tooltip copy

One line. Provides extra context, not a restated label.

Bad (restates)Good (adds context)
tooltip="Save"tooltip="Save without reloading (Cmd+S)"
tooltip="Delete"tooltip="Delete this agent and its data"
tooltip="Edit"tooltip="Edit this row" (OK for icon-only buttons)

If the explanation is > one line, use an inline helper text under the element instead of cramming into a tooltip.


Keyboard shortcut copy

In tooltips

Show shortcut at the end in <kbd> style:

tooltip="Save (Cmd+S)"
tooltip="Open command palette (Cmd+K)"

Use Cmd on Mac-style docs; the OS-aware display can swap to Ctrl at render time.

In shortcut overlays (? key)

Grouped by category, aligned:

Navigation
Cmd+K Open command palette
/ Focus search
j / k Move down / up
Editing
Cmd+S Save
Esc Cancel

i18n awareness (previewed here, full rules in 09)

Our admin app is English-first but supports translated strings via react-i18next. Copy rules that matter for i18n:

  • Don’t concatenate strings. "Found " + count + " results" breaks in many languages. Use ICU: "Found {count, plural, one {# result} other {# results}}".
  • Avoid idioms that don’t translate (“the ball is in your court”).
  • Keep labels short — translations often grow 20-40% (German famous for this).
  • Don’t hardcode plurals — use ICU plural or equivalent.

Full i18n conventions in 09-i18n-testing.md.


Copy audit checklist

Before shipping any new screen:

  • Every button label is a verb + noun (or a verb-only if unambiguous).
  • No “OK” / “Confirm” / “Submit” in dialogs.
  • No periods at the end of labels, buttons, titles, table headers, tooltips.
  • Sentence case everywhere (except product names).
  • No ALL CAPS for emphasis.
  • No exclamation marks outside greetings / congratulations.
  • No blame in error messages (“File must be under 5 MB”, not “You uploaded a huge file”).
  • Empty state has icon + title (fact) + description (next step) + primary action.
  • Canonical terms used (Agent, Tool, Workflow, Channel).
  • No raw server error text exposed to user.
  • Tooltips add context, don’t restate the label.
  • Arabic numerals everywhere.
  • No em dashes.
  • No curly quotes.
  • Single ellipsis character () for loading states.

Next: 08-accessibility.md.