---
title: 'Interface Text Guide'
source: 'https://academia.sh/en/courses/accessible-patterns/interface-text-guide'
course: 'Accessible Component Patterns'
language: en
updated: '2026-08-19T05:19:53+00:00'
license: 'CC BY-SA 4.0'
---

# Interface Text Guide

Turning an interface text guide into rules an auditor can check; a term glossary, tone rules, a per-class length limit, and the criterion that visible text be contained in the accessible name.

Every one of the seven patterns in the previous topic came down to text. A banner's
duration was computed from its message's word count, an error message's adequacy hinged
on whether it named a recovery path, and a badge's second channel was almost always a
single word.

The Micro-States lesson measured interface text as a **layout constraint**: a label's
width, the overlap truncation produces, an error message's line count. This lesson
treats the same text as a **system**: how does an interface text guide stop being a list
of personal style preferences and turn into rules an auditor can check?

## What the Guide Solves and Where It Fails

An interface text guide keeps different people working on the same interface choosing
the same words. The problem it solves is inconsistency: when one screen reads "borrow,"
another "check out," and a third "sign out," the user assumes three separate actions.

The guide fails in three situations:

- **No example.** "Write short and clear" is not a rule; it cannot be tested.
- **No counter-example.** When the wrong thing is never written down, the rule leaves
  room for interpretation.
- **Nothing audits it.** A guide read by hand stops being enforced as the catalog grows.

All three gaps point to the same fix: rules must be written so a **machine can test
them**. The guide's body can stay prose, but every rule needs an audit counterpart.

## Four Rule Families

**Term glossary.** One word is chosen per concept; its synonyms are forbidden. The
glossary is not a dictionary but a **decision record**, and the reason is written down
too. If "record" is chosen, "material" and "item" are forbidden.

**Tone.** Three rules are enough: action labels are a short imperative, system messages
never blame the user, and capitals and exclamation marks are never used for emphasis.
The no-blame rule is about accuracy, not politeness — "you entered it wrong" gives the
user no information, because it never names the shape the field expected.

**Length.** The limit changes with the text class: an action label is at most three
words, an empty-state title six, help text fourteen, a message twenty. The numbers are
arbitrary, chosen for that interface; what matters is that they can be audited.

**Visible text versus name.** The **2.5.3 Label in Name** criterion requires the visible
label to be contained in the accessible name. This looks like copy-editing, but it is a
direct text decision: writing "Apply filter" on screen while the name stays "Apply"
removes the criterion.

## Auditing the Guide

The script below audits a fourteen-entry interface text catalog against the four rule
families, then counts how many different words are used for each concept.

```js
// text-audit.mjs — checking the interface text guide's four rules against the catalog

// Interface text catalog: each entry's class, visible text, and (for the checks) accessible name.
const CATALOG = [
  { key: "button.borrow",   class: "action",  text: "Borrow",                             name: "Borrow" },
  { key: "button.reserve",  class: "action",  text: "Reserve",                            name: "Reserve" },
  { key: "button.checkout", class: "action",  text: "Check out",                          name: "Check out" },
  { key: "button.filter",   class: "action",  text: "Refine results",                     name: "Apply" },
  { key: "button.clear",    class: "action",  text: "Clear filters",                      name: "Clear filters" },
  { key: "button.export",   class: "action",  text: "Export the selected records",        name: "Export the selected records" },
  { key: "title.empty",     class: "title",   text: "No records match the filters",       name: null },
  { key: "title.landing",   class: "title",   text: "SEARCH CATALOG RECORDS",             name: null },
  { key: "error.isbn",      class: "message", text: "ISBN entered wrong!",                name: null },
  { key: "error.date",      class: "message", text: "The return date must be after today. Pick a day from the calendar.", name: null },
  { key: "help.member",     class: "help",    text: "Your member number is on your card's back, below the barcode; enter without spaces.", name: null },
  { key: "help.material",   class: "help",    text: "You can narrow the results by selecting a different material type.", name: null },
  { key: "status.result",   class: "message", text: "128 records listed",                 name: null },
  { key: "button.remove",   class: "action",  text: "Remove",                             name: "Remove type tag" },
];

// --- Rule 1: term glossary --------------------------------------------------
// One word per concept; the others go unused throughout the course.
const TERM = {
  "borrow":  ["check out", "rent", "sign out"],
  "record":  ["material", "item", "product"],
  "filter":  ["refine"],
  "member":  ["customer", "subscriber"],
};

// --- Rule 2: tone ------------------------------------------------------------
const BLAMING = ["entered wrong", "entered incorrectly", "you forgot", "you must", "did not enter"];
const ABBREVIATION = new Set(["ISBN", "QR"]);

// --- Rule 3: length ----------------------------------------------------------
const LIMIT = { action: 3, title: 6, message: 20, help: 14 };

const words = (m) => m.trim().split(/\s+/);
const lower = (m) => m.toLocaleLowerCase("en");
// A plain substring search finds "rent" inside "different" and produces a false
// positive: matching must anchor at the start of a word and leave the end open.
const matches = (text, word) =>
  new RegExp(`(^|[^\\p{L}])${lower(word)}`, "u").test(lower(text));

const findings = [];
for (const c of CATALOG) {
  // 1. term
  for (const [correct, forbidden] of Object.entries(TERM))
    for (const f of forbidden)
      if (matches(c.text, f))
        findings.push({ key: c.key, rule: "term", detail: `"${f}" instead of "${correct}"` });

  // 2. tone
  if (c.text.includes("!"))
    findings.push({ key: c.key, rule: "tone", detail: "exclamation mark" });
  for (const b of BLAMING)
    if (matches(c.text, b))
      findings.push({ key: c.key, rule: "tone", detail: `blaming pattern: "${b}"` });
  for (const w of words(c.text)) {
    const plain = w.replace(/[^\p{L}]/gu, "");
    if (plain.length >= 4 && plain === plain.toLocaleUpperCase("en") && !ABBREVIATION.has(plain))
      findings.push({ key: c.key, rule: "tone", detail: `all caps: "${plain}"` });
  }

  // 3. length
  const n = words(c.text).length;
  if (n > LIMIT[c.class])
    findings.push({ key: c.key, rule: "length", detail: `${n} words, limit ${LIMIT[c.class]}` });

  // 4. 2.5.3 — the visible text must be contained in the name
  if (c.name !== null && !lower(c.name).includes(lower(c.text)))
    findings.push({ key: c.key, rule: "2.5.3", detail: `visible "${c.text}", name "${c.name}"` });
}

console.log(`catalog: ${CATALOG.length} entries, findings: ${findings.length}\n`);
console.log("key                 rule      detail");
for (const f of findings)
  console.log(`${f.key.padEnd(20)}${f.rule.padEnd(10)}${f.detail}`);

const tally = {};
for (const f of findings) tally[f.rule] = (tally[f.rule] ?? 0) + 1;
console.log("\nby rule: " + Object.entries(tally).map(([k, v]) => `${k}=${v}`).join(", "));

// --- How many different words name the same concept -------------------------
console.log("\nconcept        words used");
for (const [correct, forbidden] of Object.entries(TERM)) {
  const seen = new Set();
  for (const c of CATALOG) {
    if (matches(c.text, correct.split(" ")[0])) seen.add(correct);
    for (const f of forbidden) if (matches(c.text, f)) seen.add(f);
  }
  console.log(`${correct.padEnd(14)}${seen.size ? [...seen].join(", ") : "—"}   (${seen.size} form${seen.size === 1 ? "" : "s"})`);
}
```

```
catalog: 14 entries, findings: 10

key                 rule      detail
button.checkout     term      "check out" instead of "borrow"
button.filter       term      "refine" instead of "filter"
button.filter       2.5.3     visible "Refine results", name "Apply"
button.export       length    4 words, limit 3
title.landing       tone      all caps: "SEARCH"
title.landing       tone      all caps: "CATALOG"
title.landing       tone      all caps: "RECORDS"
error.isbn          tone      exclamation mark
error.isbn          tone      blaming pattern: "entered wrong"
help.material       term      "material" instead of "record"

by rule: term=3, 2.5.3=1, length=1, tone=5

concept        words used
borrow        borrow, check out   (2 forms)
record        record, material   (2 forms)
filter        refine, filter   (2 forms)
member        member   (1 form)
```

Ten findings come out of a small fourteen-entry catalog, and they fall into four kinds,
each with a different cost.

**Term drift shows up in three entries.** The last table gives its total effect: two of
the four concepts are named with two different words. For the user, that makes two
concepts look like four. It is also a translation cost: every synonym produces a
separate key and a separate decision in the translation catalog.

**All caps produces three findings** from a single title. Capitals are not a style
choice: letter outlines converge, so reading slows down, and some screen readers treat
an all-caps word as an abbreviation and spell it out letter by letter. Emphasis belongs
to formatting decisions — weight, size, position.

**The blaming pattern and the exclamation mark land on the same entry.** "ISBN entered
wrong!" does not say what was wrong, does not say what to do, and assigns the fault to
the user. The same catalog's `error.date` entry is the counter-example: "The return date
must be after today. Pick a day from the calendar." It gives the rule and the fix
together, and trips no check.

**The length limit is exceeded by exactly one entry.** "Export the selected records" is
four words, over the action limit; the width calculation in the Micro-States lesson
shows where that limit comes from.

**The 2.5.3 finding is the quietest defect.** The button reading "Refine results" on
screen has the name "Apply"; nothing looks wrong visually. Someone using voice control
who says the word on screen never invokes the button. That the audit catches this is the
direct payoff of writing the rule so a machine can test it.

The script's `matches` function also avoids a general substring trap: a plain search
finds "rent" inside "different" and would misfire. Matching instead anchors at the start
of a word and leaves the end open — so "material" and "materials" are both caught, while
"different" is not.

## Decisions That Belong in the Guide

The audit enforces the rule but does not choose it. The guide's prose section is where
these decisions, and their reasons, get written down.

**Who is being addressed.** Interface text is written in the user's language, not the
system's: "Create record" is the database's business; "Add book" is the user's.

**Who is the subject.** In system messages the subject is the system: "The request could
not be sent." In text describing the user's own action, the subject is the user: "Items
you have borrowed." Mixing the two misplaces responsibility.

**How numbers and dates are written.** This decision belongs to the next lessons; the
guide states it in a single line — never hand-formatted.

**When abbreviations are allowed.** Only if already in the user's vocabulary;
interface-internal abbreviations are forbidden.

## Common Mistakes and How to Spot Them

**A guide exists, but nothing audits it.** How to spot it: scan the catalog against the
forbidden-word list; a finding means the guide is not being followed.

**The same key holds different text in two places.** How to spot it: count how many
words are used per concept; any count above one is a decision.

**Visible text is not contained in the name.** How to spot it: run the audit's fourth
rule; a visual review cannot catch this.

**Text is embedded in code.** With no catalog to audit, no rule can be enforced. How to
spot it: check whether text is separated from code; the translation catalog set up in
the Application Architecture: Routing, State and Data course is this audit's
prerequisite.

## Summary

- An interface text guide only holds if every rule has a counterpart a machine can
  test; rules with no example and no counter-example leave room for interpretation.
- Four rule families are enough: a term glossary, tone, a per-class length limit, and
  the requirement that visible text be contained in the accessible name.
- Term drift is not just inconsistency; it multiplies keys in the translation catalog —
  in a fourteen-entry catalog, two of four concepts had two different names.
- All caps and exclamation marks are not emphasis tools; emphasis is carried by weight,
  size, and position.
- The 2.5.3 criterion cannot be caught by visual review: the label shown on screen must
  be contained in the accessible name.
- Word checks skip plain substring search; matching anchors at the start of the word and
  leaves the end open, catching inflected forms without false positives from
  coincidental substrings.

## Next Step

The heaviest finding the audit caught was an error message, and its counter-example sat
in the same catalog. The difference between the two messages is not style but
**structure**: one says what is wrong, the other says what is right and how to fix it.
The next lesson classifies error messages by their source, determines which parts are
required for each class, and builds a rule set that audits messages against those parts.
