Writing
The words are part of the interface
An error message is the highest-value writing in any product, and the place it is most often skipped. Three levels decide what is settled here and what belongs to a product. German is the default; English is the documented alternative, and both are produced by the same formatter.
The three levels
Knowing which level a decision belongs to prevents the two common mistakes: putting a product-specific choice into the shared standard, and re-deciding a shared rule in every product.
| Level | Scope | Decided | Covers |
|---|---|---|---|
| 1 — System | Every product | Once, here | Terminology for system concepts, the wording of shared components, formats, accessibility rules. Not negotiable per product. |
| 2 — Fundamentals | Every product | Once, here | Principles, gender-fair language, sentence construction, number and date formats, text accessibility. A product may add to it, never contradict it. |
| 3 — Product voice | One product | Per product | Form of address, tone, domain vocabulary, how much personality. |
Form of address is level 3, and one decision
German distinguishes Sie and Du; English distinguishes register rather than pronoun. It is a product decision — but one decision per product, never mixed within an interface.
- Public and customer-facing: German Sie; English formal-neutral — “Enter your email address”.
- Internal tools for colleagues: German Du reads as collegial; English stays the same and gets terser.
A product's own guide fills the level-3 slot in
agent/consumer-AGENTS.template.md, which is
deliberately empty here — Dessau DS has no voice of its own to impose.
Principles
- Clear over clever. One idea per sentence. Active voice. Concrete terms.
- Only as much text as the decision needs. Every extra sentence is a sentence between the reader and what they came to do.
- One concept, one term. Never a synonym for variety. If it is a “project” once, it is a project everywhere — not a “workspace” in the next sentence.
- Describe what the reader does or experiences, not what the system does. “Your changes are saved”, not “The system has persisted the changes”.
- Calm and factual. No alarm language, no exclamation marks, no judgement of the reader. Nothing “unfortunately” happened, and nobody “failed” to do anything.
- Say what to do next. Particularly in errors and empty states. A message that only reports a state is half a message.
- Never all-caps as a stylistic device. It costs legibility, defeats word-shape recognition, and some screen readers read genuinely capitalised text letter by letter.
Gender-fair language
Binding at level 2, in both languages.
German — prefer genuinely neutral over any marker
| Prefer | Instead of |
|---|---|
| Nutzende, Bearbeitende, Mitarbeitende | Benutzer, Bearbeiter, Mitarbeiter |
| das Team, die Person, alle | jeder Mitarbeiter |
| Wer ein Projekt anlegt, … | Der Nutzer, der ein Projekt anlegt, … |
No generic masculine. Where a neutral term is genuinely unavailable, a gender
star (Nutzer*innen) is acceptable as a
second choice — a fallback, not the default, because screen
readers handle it inconsistently.
Often the simplest fix removes the problem entirely: address the reader directly. “Dein Projekt” rather than “das Projekt des Nutzers”.
English
Singular they for a person of unstated gender. Never he/she, and never he as a generic. Address the reader as you wherever possible.
Numbers, dates and units
German is the default. Produced by DDS.format,
never assembled by hand — the separators, the spacing and the order all differ
per locale, and assembling them yourself gets one of the three wrong.
| Thing | Formatted | DDS.format |
|---|---|---|
| Number | number() |
|
| Amount | currency() |
|
| Percentage | percent() — takes a ratio |
|
| Short date | date() |
|
| Long date | dateLong() |
|
| Time | time() — “ Uhr” is added in the copy |
|
| Relative | relativeTime() |
|
| File size | fileSize() |
Rendered by DDS.format through
Intl, not written into this page. Switching the
locale calls DDS.format.setLocale() and
re-renders — so what you see is what a product would get.
The four that bite
percent() takes a ratio
(0.195), not a percentage. That is the
Intl convention, and it avoids the bug where a
value is divided by a hundred twice — which produces a plausible small number
rather than an obvious error.
Parse with DDS.format.parseNumber(),
never parseFloat.
parseFloat reads the German
1.234,56 as 1.234
— a silent, plausible, wrong answer, and off by a factor of a thousand.
The narrow no-break space between a number and its unit is
deliberate. Never “clean it up”: a value that wraps between the
number and its unit is read as two separate things. Use
.dds-nowrap for any multi-part value — an
account identifier, a phone number, an amount with its unit.
24-hour time in both languages. German appends “ Uhr” in running text; that is a writing rule rather than a formatting one, so it belongs in the copy and not in the formatter.
Errors
The highest-value writing in any product. Structure: what is wrong, then what to do about it. A message that stops after the first half has told the reader they are stuck without telling them how to stop being stuck.
| Instead of | Write |
|---|---|
| “Invalid input” | Enter an email address, for example name@gmail.com |
| “Please match the requested format” | Enter the reference code as four characters, a hyphen, then four more — for example 7K4M-92QX |
| “Error 500” | We could not save your changes. Your text is still in the form — try again in a moment. |
| “Field required” | Enter your full name |
| “Ungültige Eingabe” | Gib eine E-Mail-Adresse ein, zum Beispiel name@gmx.net |
Rules, in order of how often they are broken
- Distinguish “the service failed” from “there is no such thing.” One says try again, the other says check the spelling. Collapsing them leaves the reader guessing, and it is the single most common failure in search and lookup. It is also why a provider must reject rather than resolve empty.
- Never blame the reader. No “you failed to”, no “invalid”, no “illegal”.
- Say whether their input survived. “Your text is still in the form” removes the fear that retrying means retyping.
- The hint stays. The reader needs the format rule and the failure — not one replaced by the other.
- Never reveal whether an account exists in an authentication error, and match the response time in both cases.
- Summary wording matches per-field wording. Two descriptions of one problem is one too many — see the error summary.
Per element
Labels
- A noun phrase, not a question. “Email address”, not “What is your email address?”
- Never a placeholder instead of a label. It vanishes on input, fails contrast, and is announced inconsistently.
- Required stated in words —
(required)/(erforderlich). Not an asterisk, not colour. - Mark the shorter set: if most fields are required, mark the optional ones.
Buttons
- A verb naming the action: “Save changes”, “Delete project”, “Änderungen speichern”.
- Not “OK”, “Yes”, “Submit”, “Confirm”. “Confirm” beside “Cancel” tells the reader nothing about what is about to happen.
- The label answers “what happens if I press this?” — so a dialog's confirm button repeats the action, not the title.
- Destructive actions name the thing: “Delete project”, not “Delete”.
- A trailing ellipsis means a further step follows — “Rename…” opens a field, it does not rename on the spot. Leave it off an action that runs immediately, even a destructive one.
Hints
- The rule or the format, before the reader gets it wrong. “Format: 7K4M-92QX”.
- Never repeat the label.
- Present in the valid state and the error state alike.
Warnings and success
- State the consequence, not the severity. “Two collaborators will lose access”, not “Warning: permissions change”.
- Warnings before an action; errors after one.
- Success confirms what happened and where it now is. Do not congratulate the reader for using the software.
Empty states — two different situations
- Nothing yet. Explain what this is for and offer the first action.
- Nothing found. Confirm what was searched and offer a way to broaden it.
- Never a bare “No data” or “Keine Daten”. See empty states.
Loading
- Say what is happening, not that something is. “Searching addresses…”, not “Loading…”.
- Announce it in a live region — a spinner communicates nothing to a screen reader.
- Debounce. Do not announce on every keystroke.
Destructive actions: never “Are you sure?”
It asks the reader to confirm a decision without giving them the information to make it. Name the scope and the consequence instead: “This removes the project and its 47 documents for everyone. It cannot be undone.”
Say plainly whether it can be undone. The confirming button carries the verb; the cancelling one says what happens instead — “Keep project” reads better than “Cancel”, because “Cancel” is ambiguous next to a cancellable action.
Accessibility in text
Level 2, binding. Most of it is about text read out of context — which is how a screen-reader user encounters a link list, a button, a table cell.
- Link text says where it goes. Never “click here”, “here” or “read more” alone — someone listing the links on a page hears “read more” six times. “Read the setup guide” works out of context.
- An accessible name must contain the visible label (WCAG 2.5.3), so a speech-input user can say what they see.
- Never abbreviate a name in an accessible label. “IB” is not a name.
-
Icon-only controls carry a name; the icon itself is
aria-hidden. - A link that opens in a new tab says so in a visually hidden span.
- Write text that can be read aloud. Avoid constructions that depend on layout — “see the box on the right” — and never rely on visual position alone.
- Expand or explain a term of art the first time it appears.
- Give a status a word, always. Never only a coloured dot.
Example data
Demo, placeholder and test content is part of the product's quality — and it is the cheapest way to find a whole class of bug before a user does.
Do
- Realistic and invented — plausible names and addresses that identify nobody.
- Deliberately include diacritics and non-ASCII: ø, ä, ç, ł, ș, ü, å, İ.
- Vary the length. A long street name and a long surname are what find truncation.
- Include the awkward-but-real case: an address with no house number, a single-character surname.
- Reserve
example.orgfor an address that is technically live — a mailto default, an href, a value that could actually be submitted. - Everywhere an email is only read — demo data, format examples in error copy — use a real consumer domain: gmail.com, gmx.net, outlook.com, web.de, orange.fr, with an invented local part.
Don’t
- Cliché placeholders. They signal unfinished work and hide real layout problems.
- Lorem ipsum — it has the wrong word lengths and no diacritics at all.
- Real personal data, in demos, tests, fixtures, screenshots or commit messages.
- A placeholder domain that could become real.
- An
example.orgaddress in text a person only reads — a demo table, an error message — it reads as a leftover placeholder.
Why the diacritics are the point
A broken charset, a bad sort order, a truncating column, a font missing glyphs — every one of those is invisible in ASCII test data and obvious the moment the data contains ø and İ. Choosing bland example data is choosing not to find those bugs.
example.org vs. a real provider
example.org is reserved for an address that is technically
live — a mailto default, an href, a value that could actually be submitted or
dialed. That is the only case the resolve-guarantee buys anything. Everywhere
else the address is only read — a demo user list, the format example inside
an error message — invent it on a real consumer domain instead. Nobody dials
"name@gmail.com" in an error message; the realistic domain is what makes it
recognisable, where example.org reads as a leftover placeholder
to a reader who has never heard of the reserved TLD. A demo table or an error
message that uses example.org reads as staged — the cliché
placeholder rule above, applied to domains.
When the wording is not obvious
- What does the reader need to know to do the next thing?
- Write that, in one sentence, in the plainest words available.
- Remove everything that is not it.
- Read it aloud. If it sounds like software talking about itself, rewrite it from the reader's side.
- Check the level: system rule, fundamental, or this product's voice? Put it in the right place.