Lesson 21 / 25
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.
Contents
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.
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.