A page works when someone can scan it and find the one thing they came for. Headings carry that scan, so the outline is decided before the prose is written.
Four rules to start with
These four decide most content questions. Everything further down this page follows from them, and the page itself is written to obey them.
- Lead with the task. Open a page, and every section in it, with what the reader came to do. Background, caveats and edge cases come after.
- One page, one job. A page that needs two
h1s is two pages. - Structure is semantics, not size. An element is an
h1–h6because of where it sits in the outline. Its size comes from the type scale, and the two are set independently. - Scanning comes before reading. Most people read the headings, the first words of each paragraph, and the lists. Those alone have to tell the story.
Guidance that already exists
This page covers structure: the outline, the heading levels, the length of a line and the words in the interface. Wording, grammar, punctuation and spelling are written down elsewhere on this site, and are not repeated here.
| Where | What it covers |
|---|---|
| Voice and tone | What we sound like, and what to avoid sounding like |
| Writing style | Sentence case, "you" and "we", active voice, acronyms, the Oxford comma, spelling |
| Tables | Icons, links, badges and empty cells inside a row |
| Date and time | Absolute dates, relative dates and time zones |
| Accessibility | Landmarks, page titles, labels and accessible names |
| Typography | The type scale that heading sizes come from |
Every component page carries a Content tab with the wording rules for that component. Start with Button, Alert, Toast and Form primitives: most of the microcopy in a product is one of those four.
| Component | Its Content tab covers |
|---|---|
| Badge | Status labels, and keeping one word for one state |
| Tag | Truncation past about 20 characters |
| Dropdown | List items short enough to scan |
| Select | Option wording, and not repeating the first word |
| Inline link | Which words to wrap in the link |
| Standalone link | Using the same verb for the same action |
| Modal | Action labels in the footer |
| Tabs | Tab labels that predict their panel |
| Tooltip | Text only, and about 80 characters of it |
| Rich tooltip | When structured content belongs in a bubble |
| Segmented group | Whether the group needs a label of its own |
| Stepper list | Step titles and descriptions |
| Stepper nav | Step titles, and what stays out of them |
Page outline
One h1, then two to five sections on an app screen and three to seven in documentation. Past that the page is doing two jobs: split it, or move the parts few people need into an accordion.
| Rule | App screen | Documentation |
|---|---|---|
h1 per page |
Exactly one | Exactly one |
h2 sections |
2–5 | 3–7 |
| Deepest level | h3 |
h4, rarely |
| Skipping a level | Never | Never |
- The
h1says the same thing as the page<title>and as the navigation item the reader clicked. - Every section opens with its heading. Under the
h1, no more than two sentences before the firsth2. - A heading with nothing under it is not a heading. Never use one for emphasis.
Heading level and size
Pick the level from the outline, then the size from the type scale. The two are independent, so an h3 can be large and an h1 small when the design asks for it.
| Level | Style | Use |
|---|---|---|
h1 |
display-500 bold |
The page title |
h2 |
display-300 semibold |
A section |
h3 |
display-200 semibold |
A subsection |
h4 |
body-300 semibold |
Documentation only |
A component that sets its own title size keeps it. A modal, an alert and a page header are each drawn as a whole, and changing one size inside them breaks it.
Headings inside components
Components that render a title put it in a div unless you say otherwise, because most of those titles sit inside a card or a dialog rather than in the page outline. When a title does belong to the outline, set title-tag to the level the outline asks for.
Leaving it out costs a screen-reader user the page structure, which is the whole point of WCAG 1.3.1.
Accordion, Stepper list, Stepper nav, Application state, Page header and Code block all take title-tag. The Code tab shows the markup for each of them.
Writing headings
A heading labels what is under it; it is not a sentence about it. Two to six words, the keyword first, in sentence case.
- Sentence case, as Writing style sets out: the first word and proper nouns, nothing else.
- Front-load the keyword. "Billing settings", not "Settings for billing".
- Name the content or the task. "Overview", "Introduction", "Misc" and "Other" name nothing.
- No full stop. A question mark only where the section really is a question, as in an FAQ.
Text people scan
Paragraphs and line length
Put the conclusion in the first sentence and the reason after it, so a reader who stops there still has the answer. One idea to a paragraph, three or four lines on screen at most.
- Sentences under 20 words, and never over 30.
- A line of running text runs 45–75 characters. The hard limit is 80, from WCAG 1.4.8.
- Hold that limit with the
--i80-layout-measuretoken on the text container, never by breaking lines by hand. The Code tab shows where it goes.
Lists, tables or prose
Prose explains why, a list is made to be scanned, and a table is for things compared on the same attributes. Picking the wrong one is the most common reason a correct page still reads badly.
| Use | When |
|---|---|
| Prose | Explaining why, or following a line of reasoning |
| Bulleted list | Three to seven parallel items |
| Numbered list | Steps that have to be taken in order |
| Table | Items compared on the same attributes |
| Accordion | Content most people never need |
- Items in a list start the same way: all verbs, or all nouns.
- No full stop after a fragment.
- Put a sentence between two lists, or between two alerts. Never stack them.
- One level of nesting, at most.
UI text and voice
Microcopy is the label, the button, the error and the empty state: the few words that decide whether someone finishes the task. Say what happens, in the order the reader needs it.
- Buttons and links: verb plus object — "Save plan", "Delete shift". Never "Click here", never "Submit", and never "OK" on something destructive. Button has the length limits.
- Errors: what happened, then what to do. "The plan could not be saved. Check your connection and try again." Never blame the reader. A whole screen that failed is an application state.
- Empty states: what will appear here, and the action that puts it there.
- Confirmations: name the object and the consequence. "Delete 'Week 42'? This cannot be undone."
- Labels: a noun, visible, above the field. A placeholder is never a label — see Form primitives.
Voice and tone and Writing style set the voice for all of this: "you" for the reader, "we" for the organisation behind the product, active voice, present tense, plain words. Two more rules belong here rather than there.
- One language to a page. Danish interface text does not get English headings, and the other way round.
- Danish is plain Danish: short words, "du" in lowercase, "kontakt os" rather than officialese. Product and component names are never translated.
Never write "above" or "below", and never point at a place on the screen. Link to the section instead, so the text survives a change of layout and still reads correctly aloud.
Accessibility and compliance
The rules on this page are WCAG 2.2 AA, which is the bar EN 301 549 sets. That standard carries into the Danish web accessibility law for the public sector, and into the European Accessibility Act, in force for many private products since June 2025.
| Rule on this page | WCAG |
|---|---|
One h1, no skipped levels, an explicit title-tag |
1.3.1 Info and relationships |
The page title says what the h1 says |
2.4.2 Page titled |
| Headings and labels describe what follows them | 2.4.6 Headings and labels |
| Every section has a heading | 2.4.10 Section headings (AAA, adopted anyway) |
| At most 80 characters to a line | 1.4.8 Visual presentation (AAA, adopted anyway) |
| Link text says where the link goes | 2.4.4 Link purpose |
Accessibility covers the rest of a page's structure: landmark roles, the page title on a route change, and accessible names.
Set the heading level
Components render their title in a div by default, so no component ever claims a place in your outline by accident. Set title-tag on the element that carries the title, at the level the outline asks for.
Accordion items
title-tag goes on each <i80-accordion-item>, not on the accordion around them. The accordion passes size and type down to its items; it does not pass this.
<h2>Billing</h2>
<i80-accordion>
<i80-accordion-item title-tag="h3" heading="When am I charged?" open>
On the first of the month, for the month before.
</i80-accordion-item>
<i80-accordion-item title-tag="h3" heading="Can I change the card?">
Yes, under Billing settings.
</i80-accordion-item>
</i80-accordion>
Page header and application state
Page header is the one component that renders an h1 by default, because a screen's name is that screen's h1. Set title-tag only when the header sits in a page that has its h1 somewhere else.
<i80-page-header title="Q3 plans"
description="Everything the team is working on this quarter."></i80-page-header>
An application state fills the screen, so on a dedicated error or empty page its title is the h1. Inside a panel on a page that already has one, it is an h2 or lower.
<i80-application-state title="Page not found" error-code="404" title-tag="h1">
<p>Check the address, or go back to your plans.</p>
<i80-link-standalone slot="footer" href="/plans" icon="arrow-left">
All plans
</i80-link-standalone>
</i80-application-state>
Steps and code blocks
A stepper list and a stepper nav take one title-tag for all of their step titles. Give them the level below the heading of the section they sit in.
<h2>Release checklist</h2>
<i80-stepper-list label="Release checklist" title-tag="h3">
<i80-stepper-step status="complete" title="Scope agreed"></i80-stepper-step>
<i80-stepper-step status="processing" title="Review"></i80-stepper-step>
<i80-stepper-step title="Launch" description="21 September"></i80-stepper-step>
</i80-stepper-list>
A code block puts its title in a p, which is right for a sample sitting inside a paragraph of prose. Raise it when the sample is a section of its own.
<i80-code-block title="Install the stylesheet" title-tag="h3"
language="bash" value="npm i @i80/design-system-components"></i80-code-block>
Keep lines readable
--i80-layout-measure is 70ch: the width running text stops at. Put it on the container that holds paragraphs and lists, and leave tables, code blocks and component previews out of it — those need every pixel of the column.
.article__body > p,
.article__body > ul,
.article__body > ol,
.article__body > blockquote {
max-width: var(--i80-layout-measure);
}
This site works the same way. The documentation body caps its paragraphs, lists and blockquotes at the token, while tables, code blocks, previews and the do-and-don't panels keep the full width.
Checks we plan to add
Rules nobody checks drift. Two checks are planned for this repository, and neither is in place yet.
- Vale over the Markdown: sentence-case headings, a warning on sentences past 25 words, and a short list of words to avoid — "utilise", "in order to", "simply", "click here".
- A heading-outline check over the built site: one
h1and no skipped levels, run from the Playwright suite that already covers these pages.
Both are meant to run as a Gitea Actions job, over this documentation and over any product built with i80. The first version warns and does not fail a build, so that a drifting page is visible before it is blocking.