Docs are the part of your product most likely to be read by someone who can't see the screen, can't use a mouse, or has a cognitive load budget you don't know about. WCAG 2.2 AA is the baseline every docs site should clear. Most of it is cheap to get right on day one and expensive to retrofit later.
This guide covers the accessibility patterns specific to docs. For the WCAG 2.2 AA full reference, see the W3C spec.
The big four
Keyboard navigation
Every interactive thing reachable + operable with Tab / Shift+Tab / Enter / Space. No keyboard trap. Focus outline always visible.
Screen reader compatibility
Structured headings, descriptive link text, alt text on images, ARIA labels on icon-only buttons. The page makes sense narrated aloud.
Colour + contrast
4.5:1 contrast for body text, 3:1 for large text + UI. Never rely on colour alone to convey meaning (icons + text labels).
Reading order + language
DOM order matches visual order. <html lang="en"> set. Plain language, expanded acronyms on first use, no jargon without definition.
What NookDocs ships accessibility-wise
Out-of-the-box behaviours every tenant gets:
Skip-to-main-content link at the top of every page. Hidden until focused; press Tab once to jump past the nav.
Semantic HTML —
<main>,<nav>,<article>,<header>,<footer>wherever appropriate. Screen readers use these as landmarks.Focus outlines always visible (CSS
outlinenot overridden tonone).<html lang="...">from the tenant's language config or defaulten.Theme tokens for contrast — every theme's light + dark palette hits WCAG AA minimum on body text.
Descriptive page titles —
<title>per page so screen-reader users hear where they are.
What's on you as the author:
Writing alt text for images you add.
Keeping heading hierarchy clean (see style and tone).
Not overriding link colours to match paragraph colour.
Not inventing custom interactive components that lack keyboard handlers.
Keyboard navigation
Every interactive element must be reachable with the keyboard:
Tab order follows visual order
DOM order = visual order = keyboard order. If a sidebar visually comes before the main content, it should come first in the DOM. NookDocs layouts enforce this.
Every interactive element has a visible focus indicator
A blue outline, a background shift, a ring — whatever your theme uses, it must be visible against both light and dark backgrounds. Don't outline: none without replacing with :focus-visible { box-shadow: ... }.
Enter and Space activate buttons
<button> elements handle both natively. Custom components built with <div onClick> don't — avoid them, or add onKeyDown + role="button" + tabindex="0".
Escape closes overlays
Search modal, contextual menu, mobile drawer — all dismissable with Escape. NookDocs's built-in components already handle this.
Test: unplug your mouse, navigate every page by keyboard. If you can't reach something, can't see where focus is, or get stuck — fix it.
Screen reader compatibility
Screen readers (VoiceOver, JAWS, NVDA) narrate the page aloud. They rely on structure, not visuals.
Headings as landmarks
Screen reader users navigate by heading — press H in JAWS/NVDA to jump heading by heading. Clean hierarchy matters more than for sighted readers:
One
H1per page (the page title).Don't skip levels (no
H4directly afterH1).Use headings for structural sections, not for visual sizing.
Alt text for images
Every <Frame> / <Image> needs an alt attribute. Rules:
Describe the content relevant to the surrounding text. For a screenshot of a dashboard showing analytics:
alt="Analytics dashboard showing 1200 daily page views and a search bar at the top".Decorative images get
alt=""(empty, not missing). Screen readers skip over them.Don't repeat surrounding text. If the caption says "Analytics dashboard with page views", the alt text can be shorter:
alt="Dashboard UI".Avoid "image of" / "picture of" — the screen reader already announces "image".
<Frame caption="The Custom Domains panel after adding docs.acme.com">

</Frame>Descriptive link text
Screen reader users pull up a list of all links with Insert+F7. If every link says "click here" or "read more", the list is useless.
❌ To add a custom domain, [click here](/deploy/custom-domain).
✅ Add a custom domain in the [deploy guide](/deploy/custom-domain).
✅ For DNS record specifics, see [custom domain setup](/deploy/custom-domain).ARIA labels on icon-only buttons
Buttons that render only an icon (no text) need aria-label:
<button aria-label="Close search">
<XIcon />
</button>NookDocs's built-in interactive components handle this — the danger is when you build custom widgets. Always add an aria-label if there's no visible text.
Code blocks
Fenced code blocks render with role="region" and aria-label="Code example" (implicit from the <pre><code> elements). Screen readers announce the language when the fence has one: ```bash announces "bash code example".
Multi-block <CodeGroup> tabs have proper ARIA — screen reader users hear "tab 1 of 3, cURL" and can arrow between tabs.
Colour + contrast
Text contrast thresholds
WCAG 2.2 AA minimums:
| Text size | Minimum contrast |
| Body text (< 18pt) | 4.5:1 against background |
| Large text (≥ 18pt or ≥ 14pt bold) | 3:1 |
| UI components (buttons, focus outline, borders of interactive things) | 3:1 |
Tools:
Browser DevTools → Inspect an element → "Contrast" in the Colours panel. Works in Chrome + Firefox + Safari.
WebAIM Contrast Checker — paste hex values.
Stark — Figma / browser plugin, real-time checks.
Every NookDocs theme ships palettes that meet WCAG AA on --docs-fg against --docs-bg. When overriding via colors.primary / background.*, test the combination before shipping.
Never rely on colour alone
Red/green distinctions fail for ~8% of men. Always pair colour with an icon or label:
❌ Colour-only
<Badge variant="success">Active</Badge>
<Badge variant="danger">Failed</Badge>
✅ Colour + icon
<Badge variant="success"><CheckIcon /> Active</Badge>
<Badge variant="danger"><XIcon /> Failed</Badge>The text label alone usually suffices; icons reinforce for scanning.
Dark mode
Auto-adaptive by default via prefers-color-scheme. Manual toggle via the theme button in the header. The <Visibility> component's agent/human split doesn't interact with dark mode — both variants render through both palettes.
Don't override --docs-fg-dark / --docs-fg-muted-dark without re-testing contrast.
Reading order + language
Reading order
DOM order = what screen readers narrate. If your CSS re-orders elements visually (flexbox order, grid areas, absolute positioning), screen reader users hear the original DOM order, not the visual one.
NookDocs layouts keep DOM == visual. When writing MDX, structure top-to-bottom in the order you want it read.
Language attribute
<html lang="en"> set on every page automatically from tenant language config. Multi-language tenants use the language's slug. Critical for:
Screen reader pronunciation (reads English text with English phonemes)
Automatic translation tools
Search engine indexing
If a specific word or phrase is in a different language, wrap it:
The Japanese word <span lang="ja">ドキュメント</span> means "document".Plain language
Technical writing can be precise without being jargon-dense. Every acronym defined on first use. Every domain-specific term linked to a glossary entry or explained in-line.
Flesch-Kincaid reading-ease score of 60+ is a good target for docs — roughly 8th-grade reading level. Tools:
Hemingway Editor — pasteable
VS Code extension
tammersaleh.write-good— inlineYour LLM of choice — "score this passage for reading ease"
Motion + animation
WCAG 2.3 AAA: respect prefers-reduced-motion. Users with vestibular disorders can literally be nauseated by decorative parallax.
NookDocs's built-in components honour the preference — Framer Motion animations respect prefers-reduced-motion: reduce. When adding custom motion:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}Good default: turn motion OFF when the user asks, turn it on when they don't.
Forms + inputs
(Less relevant for docs, but feedback widgets + search inputs apply.)
Every
<input>has a<label for="...">oraria-label.Required fields marked with
aria-required="true"+ visible indicator (asterisk, the word "required").Error messages have
role="alert"oraria-live="polite"so screen readers announce them.Don't validate on every keystroke — only on blur or submit. Frustrating announcements.
Testing protocol
Weekly, on a representative page:
Lighthouse accessibility audit
Chrome DevTools → Lighthouse → Accessibility category. Target: 95+. Covers 30-40% of WCAG via automated checks.
Keyboard-only navigation
Unplug your mouse. Navigate the page. Can you reach every link, button, toggle? Is focus always visible?
Screen reader spot-check
macOS: Cmd+F5 to enable VoiceOver, read the page. Windows: NVDA (free) or JAWS. Listen for 2 minutes. Does the page make sense narrated?
Contrast check
DevTools → Inspect any text → Contrast ratio. Target: 4.5:1 body, 3:1 UI.
prefers-reduced-motion
macOS: System Preferences → Accessibility → Display → Reduce motion. Visit the page. Animations should be ~0ms.
Common accessibility mistakes
Related
Style and tone — plain language conventions, foundations
Media guide — alt text patterns + image-specific a11y
WCAG 2.2 AA reference — the standard itself
WebAIM — practical accessibility resources + testing tools
SEO for docs — sibling guide covering on-page signals