Accessibility is not a layer applied to a finished interface. It is whether what you built can be operated by someone not driving it the way you did — by keyboard, by voice, at 400% zoom, on a phone in the sun. Most of the work is choosing markup that already carries meaning; a smaller part is filling gaps markup cannot express; the stubborn part is focus, which nothing does for you. The standards read like a checklist, and reading them that way is the mistake this guide is written against.
Each chapter opens with the short version. Tap one to read the detail.
What it is, and what a conformance claim asserts
~2 min
Barriers are situational as often as permanent: a broken arm, bright sun and a small screen break the same designs. Requirements sit under four principles — perceivable, operable, understandable, robust. Conformance is all-or-nothing per page and per process, which is why the checklist framing misleads.
The audience is wider than the word disability suggests: people on small screens, older users whose sight and dexterity have changed, someone with a broken arm, anyone reading in bright sun or without audio. One decision fails all of them at once, which is why the work generalizes: it is mostly about not assuming how the interface gets driven.
Requirements sit under four principles. Content must be perceivable (text alternatives, media, adaptable structure, distinguishable presentation), operable (keyboard, enough time, seizure safety, navigation, input modalities), understandable (readable, predictable, input assistance) and robust. The Web Content Accessibility Guidelines (WCAG) organize criteria that way, and Accessible Rich Internet Applications (ARIA) supplies semantics markup cannot express; the Web Accessibility Initiative (WAI) publishes both.
Conformance has three levels — A, AA, AAA — each containing the ones below, and AAA is not recommended as blanket policy for a whole site. Two rules make a claim stricter than people expect: it applies to a full page and cannot be earned by excluding part of one, and every page of a multi-step process must conform. Suppose a checkout runs cart, address, payment, confirmation — if the confirmation page fails one criterion, the claim fails for all four.
A technique also counts only if it is accessibility supported — genuinely supported by the software your audience has. And four criteria bind even non-conforming content: audio control, no keyboard trap, three flashes, pause-stop-hide.
Rule: read a criterion as the description of a barrier, never as a box — the question is whether the person finished the task.
Name, role, value — and where a name comes from
~3 min
The browser builds an accessibility tree and exposes it to assistive software. Every component must expose a name, a role and its state — which native elements already do; ARIA changes only what an element claims to be, adding no focus and no key handling. The name comes from a fixed cascade.
Beside the page you see, the browser keeps an accessibility tree: accessible objects representing the interface, which assistive software reads instead of your markup. ARIA does not rewrite the document; it changes how elements are exposed there.
Three kinds of information live there: a role says what a thing is, a state changes as someone works — expanded, checked, selected. The criterion requires every component to expose a name and a role, any user-settable state to be programmatically settable, and changes to be announced. Standard HTML controls do that already; build one yourself and it all becomes your job.
Which is the whole argument for native elements. Each carries an implicit role: nav is navigation, aside is complementary, an anchor with href is link. Restating a role an element already has is discouraged, and a role conflicting with an element's semantics is not permitted at all.
The first announces itself as a button and is one in no other respect: not focusable, dead to Enter and Space, no disabled state. A wrong role is worse than none — the element now lies about itself.
The name is not a field you set either — an algorithm computes it, and its order explains most naming bugs. The browser looks for aria-labelledby, then aria-label, then the host language's own labeling (a label, an alt, a caption or legend), then the element's own text if its role permits, and only then title — so a tooltip is the weakest name available.
aria-labelledby repays knowing exactly: it processes its id references in attribute order, not document order, joining each with a space. A referenced node contributes its text even when hidden, which makes a visually hidden span a legitimate naming tool.
The trap is aria-label: it replaces the visible text entirely and is invisible to sighted readers and to translation. Speech-input users activate controls by saying what they see, so a criterion exists precisely here — the accessible name must contain the visible label text.
<button aria-label="Send form">Submit</button>
Nothing warns you. The speech user says "click Submit" and the button ignores them.
Rule: pick the element that already has the role, and name it with aria-labelledby pointing at the words on screen.
Structure is the interface
~2 min
Whatever a relationship looks like, it must also exist in markup: headings as headings, lists as lists, cells tied to their headers. Instructions cannot lean on shape, color, size or position alone, and the page's language must be declared or pronunciation is guesswork.
For someone using a screen reader, structure is navigation: headings are the outline they move through, landmarks are jump targets, and a data table is readable only if each cell knows which headers describe it. The misconception is that headings are sizes — pick h4 because it looks right and a reader jumping by heading hears a subsection with no section above it. The criterion says information, structure and relationships conveyed through presentation must be available programmatically or in text. Hence real heading and list elements, and table markup using scope or paired headers and id — and hence faking a table with whitespace as a named failure.
Strip scope and a listener hears "$9" with no idea which column it belongs to.
Instructions carry the same obligation. "Press the round green button on the right" leans on shape, color and position; the criterion names shape, color, size, visual location, orientation and sound. The fix is not to delete the visual cue but to add an identifier that does not require sight — "the Next button, lower right" passes.
Declaring language is the one-attribute version: a page's default language lets screen readers load the right pronunciation rules and players show captions correctly. Marking an individual foreign phrase is a separate criterion.
Rule: if the design communicates a relationship, the markup has to communicate it too.
Images and media: serve the purpose, not the appearance
~2 min
A text alternative serves the same purpose the content serves — a different question from describing how it looks. A functional image is named for what it does, a decorative one marked so assistive software skips it, and captions carry sound, not only dialogue.
The criterion asks for a text alternative serving an equivalent purpose, then narrows what equivalent means. A control gets a name describing its purpose. Time-based media, tests and sensory experiences get a descriptive identification rather than a substitute — a paragraph cannot replace a piece of music. Decorative content must be implemented so assistive software ignores it entirely.
<img src="chart.png" alt="Signups doubled after the March redesign">
<a href="/cart"><img src="cart.png" alt="Cart"></a>
<img src="swirl.png" alt="">
The first carries the point the chart makes, not its shape — the common mistake is describing the picture, and "photo of a bar chart" names the medium while withholding the finding. The second names the link's destination, because here the image is the control. The third is empty deliberately: the image says nothing, so it should say nothing. A missing alt is different — an absent attribute leaves assistive software guessing, often from the filename. Complex content needs two lengths: a chart takes a short label plus a longer description or an adjacent data table.
Video splits into obligations people routinely merge. Captions cover speech and the non-speech audio that matters — effects, music, who is speaking — which is what separates them from subtitles, written for people who can hear everything else. A transcript is a third artifact: it serves audio-only material and never substitutes for captions on video.
Rule: ask what this content does on the page, then write that down.
Presentation the user is allowed to change
~2 min
Contrast is a luminance ratio, not a judgment call: 4.5:1 for body text, 3:1 for large text, and 3:1 for the borders and focus rings that let you find a control. Separately, layout must survive 400% zoom without forcing you to scroll in two directions.
Body text needs a contrast ratio of at least 4.5:1; large-scale text needs 3:1, where large means at least 18 point, or 14 point when bold — roughly 24 and 18.5 pixels. Disabled controls, decoration, logotypes and incidental text are exempt. The ratio is arithmetic, not taste: it divides the lighter color's relative luminance by the darker one's, each offset by 0.05 — so two colors can look very different and still fail.
The trap is trusting a contrast pass. Checkers measure text, so the outline that locates a field and the ring that shows focus go unmeasured — yet a separate criterion covers exactly those: the information needed to identify a control and its state, and graphics needed to understand content, must reach 3:1 against the colors beside them.
color: #333; /* the label everyone checks */
border-color: #9d9d9d; /* 2.7:1 on white — a documented failure */
The label passes; the box you must click does not.
Color alone is a third, independent rule: it may not be the only means of conveying information, indicating an action or distinguishing an element. Add a word, an icon, an underline, a pattern. A red border on an invalid field, with nothing else, is exactly this failure.
Layout has its own rules. Text must reach 200% without losing content or function, and content must reflow at 320 CSS pixels wide — what a 1280-pixel viewport becomes at 400% zoom — with no two-dimensional scrolling. Content genuinely needing that layout, like a map, is excepted.
Rule: measure the pixels that carry the meaning, and test the layout at the width someone zoomed into.
Keyboard operability, and the focus ring you deleted
~2 min
Every function must be reachable from a keyboard, focus must never get stuck, and the indicator must stay visible — removing the outline with no replacement is how shipped CSS usually breaks this. Repeated blocks need a bypass, and a focused control must not be entirely hidden.
All functionality must be operable through a keyboard interface, with no dependence on the timing of individual keystrokes. The only exception is input genuinely depending on the path a pointer travels, such as freehand drawing; dragging to a target is not exempt.
tabindex is the mechanism people misuse. 0 places an element in the tab sequence in document order; -1 makes it focusable by script while keeping it out. Positive values are strongly advised against, because one element then reorders the whole document around itself.
Focus visibility is where handsome code fails. The misconception is that outline: none is a style decision; it deletes the only cursor a keyboard user has, and styling the indicator away is a documented failure. The fix is not to tolerate a ring after every mouse click.
:focus-visible matches only when the browser judges the user needs to see where focus is — keyboard interaction, typically not a click on a button. Whatever you draw still has to clear 3:1 against its surroundings.
Two navigation rules finish the picture. Repeated blocks — the forty navigation links above the article — need a bypass: a skip link, or headings and landmarks to jump between. And a focused control must not be entirely hidden by your own content, which is what a sticky footer does to the field beneath it.
Rule: put the mouse down. Anything you cannot reach, see, or escape is a defect.
Custom widgets: the contract you signed
~2 min
Building a dialog, a tab set or a combobox means taking on its whole keyboard contract, not just its roles. A composite widget is one tab stop with arrow keys inside. Content that changes without moving focus must be announced through a live region, or nobody hears it.
There is a published catalog of widget patterns — dialog, combobox, tabs, listbox, menu, tree — each stating the keyboard behavior, not just the roles.
The misconception is that a modal is a div with an overlay. Build the look without the focus contract and Tab walks straight out into the page behind, which is still fully operable. The dialog is almost entirely about focus:
•
move focus into the dialog when it opens
•
keep Tab and Shift+Tab cycling inside it
•
close on Escape
•
return focus to the control that opened it
•
make everything behind it inert, unreachable by pointer and by assistive software
const opener = document.activeElement; // before opening: who had focus
// ... open, trap, Escape ...
opener.focus(); // on close: hand it back
Give it role="dialog", aria-modal="true", and a name from aria-labelledby or aria-label.
Composite widgets follow one principle: one tab stop per widget, arrow keys inside. With a roving tabindex, one child holds 0 and the rest -1; on an arrow key you flip the old child to -1, the new one to 0, and focus it. The alternative, aria-activedescendant, keeps focus on the container while pointing at the active child by id.
Changes that do not move focus are a separate problem: a result count, a save confirmation, a validation summary must each be exposed so assistive software can present it without focus moving. role="status" suits results and state, role="alert" errors and warnings — and an alert deliberately does not take focus. Polite waits for a pause, assertive interrupts immediately, and assertive for routine updates is itself a failure.
Rule: invent a widget and you have signed for its keyboard contract — reach for the documented pattern first.
Forms, errors, and no surprises
~2 min
Labels must be there for everyone, errors described in words rather than a red outline, and nothing may change context merely because a control received focus. Sign-in counts too: forcing people to memorize or transcribe, or blocking paste, has its own criterion.
Forms are where most real failures live.
Labels first. Content requiring input needs labels or instructions presented to all users, not hidden and exposed only to assistive software. Since the name computation ranks a tooltip last and the host language's own label far above it, an associated label element is the simplest and most durable.
Errors second. If an input error is detected automatically, the field must be identified and the error described in text. Color and styling are welcome as reinforcement but never sufficient — which is what makes a bare red border a failure. A further criterion asks you to suggest the correction when you know it.
<label for="email">Email</label>
<input id="email" aria-describedby="email-err" aria-invalid="true">
<p id="email-err">Enter an address that includes an at sign.</p>
No surprises third. A change of context — a new window, a viewport change, a focus move, or content changing in a way that alters the page's meaning — must never be triggered merely by receiving focus. On activation it is fine; on focus alone it strands everyone tabbing through.
Authentication deserves its own note. A step requiring you to remember, manipulate or transcribe information is a cognitive function test, disallowed unless you also offer an alternative, a supporting mechanism, object recognition, or the person's own content — which is what makes a blocked paste field, or a form fighting password managers, a real defect, not a preference.
Rule: label it visibly, say what went wrong in words, and never act on focus.
Touch, motion, and time
~2 min
Any path-based or multi-finger gesture needs a single-pointer alternative, and targets want 24 by 24 CSS pixels or equivalent spacing. Time limits must be adjustable, flashing is capped at three per second, and interaction-triggered motion should honor the reduce-motion setting.
The misconception is that touch is forgiving. The person who built the swipe-only carousel already knows the gesture, so it tests fine and ships broken. Functionality driven by a path-based gesture — a swipe, a flick, tracing a shape — or a multipoint one — pinch to zoom, a two-finger swipe — must also work with a single pointer and no path, unless it is essential; a signature is the standard example. A map with pinch zoom passes once it also has plus and minus buttons; a newer criterion does the same for dragging.
Target size is explicit: at least 24 by 24 CSS pixels. A smaller one can still pass on spacing — picture a 24-pixel circle centerd on each undersized target, and require that no circle overlaps another. For example, two 20-pixel icon buttons pass only if their centers sit 24 pixels apart. Targets inline in a sentence are exempt.
Time is a barrier for anyone reading or typing slowly. Where you set a limit, the user must be able to turn it off, adjust it to at least ten times the default, or be warned and given twenty seconds to extend it — repeatable ten times over. Real-time and essential limits are excepted.
Two motion rules close it out. Nothing may flash more than three times in a second unless it stays under the defined thresholds — protecting people with photosensitive seizure disorders, and binding the whole page because such content wrecks everything on it. And interaction-triggered motion — parallax, scroll-driven movement — should be disableable, which the reduce-motion setting already tells you.
Rule: every gesture needs a tap that does the same job, and every timer needs a way out.
Testing: drive it the way people actually do
~2 min
No tool can decide whether a site conforms; knowledgeable human evaluation is required. A checker proves an attribute exists, never that it means anything. The real test is driving the page as people do — keyboard only, at 400% zoom, by voice — and reading the name and role exposed.
Automated checkers are useful and they are not evidence. The published guidance states plainly that no tool alone can determine whether a site meets the standard: knowledgeable human evaluation is required. The reason is structural, not a gap someone will close. A rule engine sees that an alt attribute is present, not whether it says what the image is for; that a control has a name, not whether that name matches the words printed on it; that headings exist, not whether the outline makes sense.
<img src="revenue.png" alt="chart">
That passes every scan and tells the reader nothing.
So test the way the page is driven. The published list of tools names screen readers, magnifiers for low vision, speech recognition and selection switches for people who cannot use a keyboard or mouse, plus adaptive strategies such as larger text and captions. Each changes what working means: speech users say what is printed on a control, so a name missing those words fails silently; at 400% zoom someone reads a column 320 pixels wide, so an error summary atop a long form is off screen while they fix a field at the bottom.
Three checks need no tooling. Tab through the page, watching where focus lands and whether you can always see it. Zoom to 400% and look for content cut off or scrolling sideways. Open the browser's accessibility inspector and read the name, role and state a control exposes — the real contract, and often not what the markup implies.
The same holds off the web: the guidelines cover mobile devices, and companion guidance extends them to native and hybrid apps, where a view sets name, role and state through platform calls rather than markup. And nothing substitutes for including people with disabilities throughout development.
Rule: treat automation as a lint pass; the barriers that matter are found by a person trying to finish a task.
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.