Skip to main content
Steve McGarvey
CASE STUDY

Accessibility Documentation System

An annotation kit, a documentation template, and an audit template that give accessibility decisions a place in the design file.

Relevant skills:
  • Design systems
  • Accessibility
  • Design-to-development handoff
  • Designer enablement

At a glance

Problem
The design system had no accessibility documentation, so focus order, alt text, and ARIA (Accessible Rich Internet Applications) were decided by default, by whoever touched the code last.
What I decided
I dropped my custom annotation system for a proven public kit, rebuilt it with the pieces it lacked (errors, dynamic content, ARIA targets), and paired it with documentation and audit templates in the same ten categories.
Result
A system built and taught: released, piloted, and backed by 15 training sessions. Consistent use is limited, about 3 in 10 designers by my own count in design reviews.
ContextEnterprise design system at an enterprise insurer
My roleDesigned and built the accessibility annotation system, its documentation and audit templates, and the designer training
Built onCVS Health Web Accessibility Annotation Kit, v2.8.0, licensed CC BY 4.0
Used byProduct designers, who annotate, and engineers, who build from the annotations
ToolFigma
TimelineCustom system started in the first quarter of . Switched to the CVS base early in the second quarter of , after axe-con . Released for use and piloted in the first quarter of . Training built after release and delivered in the second and third quarters of .

Style doesn’t decide semantics

A designer asked me to review a form. Above an address block (street, apartment, city, state, ZIP) sat the question “What’s your address?” set in a large, bold style. It was the section’s heading and needed to be an H2 (a level 2 heading), but she hadn’t marked it yet.

Below the block were several standalone fields. Their labels used the same type style as the heading, so she pinned each one as an H2.

The mistake is easy to make, and screen reader users are the ones who pay for it. Every one of those labels would announce as a section heading, and anyone navigating the page by headings would land on a list of field labels. Using the annotation kit and its documentation, I walked her through the difference. The address question is a heading because it introduces a section. The field labels only look like headings. Style doesn’t decide semantics.

She corrected her pins, grouped the address fields as a fieldset named by its heading, and engineering got the right guidance before build.

The rule runs the other way too. On another flow, a designer marked a line set in plain body text as an H2, because it introduced the section beneath it. That was the right call, and I backed it with the documentation. A later design brought the visual style in line with the structure.

Before this system, neither conversation could have happened in design at all. There was nowhere in the design file to have them.

Two versions of the same form, side by side. Each has the bold heading "What's your address?" above street, apartment, city, state, and ZIP fields, followed by two standalone fields, Phone number and Email address, whose labels use the same large bold style as the heading. On the left, labeled Before, all three bold lines carry purple h2 pins. On the right, labeled After, only the address question keeps its h2 pin, a dashed outline labeled "Fieldset: named by heading" wraps the address fields, and the two standalone labels carry dark Input pins numbered 1 and 2.(opens full size in a new tab)
Same form, same type styles. The pins on the right separate what looks like a heading from what is one. Redrawn example with neutral content. Select the image to view it full size in a new tab.

The problem

When I started, the design system had no accessibility documentation. The docs hadn’t failed anyone. They didn’t exist.

Designers knew about contrast and could measure it, but there was no consistent way to record the result in a design file. So accessibility conversations started after the work was already in engineering’s hands.

Everything else was assumed or guessed. Nobody reviewed focus order; the team trusted that developers would get it right. Alt text worked the same way. ARIA was engineering’s best guess, built from web searches and intuition.

Each of those is a design decision. Without a place to record it, the decision got made by default, by whoever touched the code last.

Designers had asked for this. In a workshop, they ranked a standard annotation process, a checklist, and a review cycle as top needs.


Choosing a base

I started by building a custom annotation system. Partway in, I attended a session at axe-con where Adobe described adopting the Web Accessibility Annotation Kit from the CVS Health Inclusive Design team. The kit was public, and another large design organization was already using it. A base that was proven elsewhere beat one I’d have to prove myself, so I dropped my custom work and rebuilt on top of the kit.

The base was version 2.8.0, still the current public version. Credit for the original structure and visual language belongs to that team. Most of what you see in the rest of this case is mine.

Two white panels. The left, titled Parent kit, lists nine colored annotation chips from the CVS Health Web Accessibility Annotation Kit, version 2.8.0: Button, Heading, Image, Landmark, Link, Input, Focus order, Reading order, and Note. The right, titled Rebuilt kit, lists the same nine, then three more rows highlighted in yellow and tagged Added: Error, for page-level and field-level error tables; Dynamic content, for five pattern cards; and ARIA target, for pins showing what aria-labelledby and aria-describedby point to. A note below adds screen reader output components and the documentation and audit templates.(opens full size in a new tab)
The parent kit's annotation types on the left, the rebuilt kit on the right. The highlighted types didn't exist in the original. Parent kit: CVS Health Web Accessibility Annotation Kit, CC BY 4.0, linked in the table above. Redrawn and simplified by me. Select the image to view it full size in a new tab.

What I built

The two systems look alike. Underneath, almost everything has changed.

Annotations for where accessibility breaks most

The parent kit had no way to document errors or dynamic content. Those are two places where products routinely fail screen reader users: error messages that never get announced, and content that changes without telling anyone. Leaving them out meant designers stayed silent on the hardest parts of the interface, and engineering kept guessing.

I built an Errors block and a Dynamic content block.

The Errors block works at two scopes. A page-level table covers errors that belong to the whole form, and a separate table for each input covers that field’s own errors. A numbered error stamp on the design points to the matching table. Every row pairs the trigger that causes the error with the exact error text the user sees. That turns “show an error” into a list engineering can build and quality assurance (QA) can test against, one row per case.

The Dynamic content block comes in five variants, one per pattern: modal or dialog, tooltip, popover, accordion, and inline section. Each variant asks only the questions that pattern needs. A modal records its dismiss behavior (close button, call to action, Escape, click outside), where focus returns when it closes, its role, and aria-modal. A tooltip records whether hover, focus, or click triggers it. An accordion records its default state. Every variant names its triggering element by stamp number, so the card points back to the exact button or input on the design.

Each card also carries a yellow note for the designer, deleted before handoff, that lists which attributes the trigger should use and which it shouldn’t. For a modal, the trigger takes aria-haspopup and aria-controls, but not aria-expanded. For an accordion, the trigger takes aria-expanded and aria-controls, but not aria-haspopup. Those are the distinctions engineers were guessing at before, and the note puts the answer next to the decision.

On the left, a Shipping address form with an error summary box at the top reading "There is 1 problem with this form," tagged with a red PAGE Error pin, and a ZIP code field containing "123" with a red border, the message "Enter a 5-digit ZIP code," and a red pin numbered 1. On the right, two tables edged in red. Page-level errors lists two triggers and their messages: submitting with errors, and a session timeout. ZIP code errors, tagged 1 to match the field's pin, lists three: left blank, fewer than 5 digits, and contains letters, each with its exact error message.(opens full size in a new tab)
Each error case gets one row: what triggers it, and the exact words the user sees. The pin number ties the field to its table. Redrawn example with neutral content. Select the image to view it full size in a new tab.
On the left, a Shipping address section with an Edit address button tagged by a green "1 Button" pin. Below it, the open state: an Edit address dialog over a dimmed background, tagged by a blue "1 Dynamic" pin, with pink target pins numbered 1 beside the dialog title and 2 beside the description "Changes apply to future orders only." On the right, a Dynamic Content Details card with rows for type (modal or dialog), triggering element (the 1 Button chip), dismiss behavior (close button, Cancel, Esc), return focus (triggering element), role (dialog), aria-modal (true), aria-labelledby (target 1), and aria-describedby (target 2). Under it, a yellow designer note lists the trigger's attributes: use aria-haspopup and aria-controls, don't use aria-expanded, and give the button a clear accessible name.(opens full size in a new tab)
The modal variant of the Dynamic content card. Every row points back to something on the design by number, and the yellow note answers the attribute questions engineers used to guess at. Redrawn example with neutral content. Select the image to view it full size in a new tab.

ARIA targets

The parent kit had no ARIA targets either. They’re small numbered pins, placed next to the elements an ARIA relationship points to. When a dialog card lists aria-labelledby 1 and aria-describedby 2, matching pins beside the dialog’s title and description show which text names the dialog and which describes it. That moved a decision engineering used to make from search results into the design file, where the designer can see it and a reviewer can check it. Screen 04 shows them in use.

Structure under the surface

Most of the rebuild doesn’t show in a screenshot. I rebuilt the blocks with auto layout and regrouped their layers so each block’s structure communicates what it is. I also debugged the file itself: broken components and variants, key-value pair issues in the detail cards, and broken links to MDN (Mozilla Developer Network) reference pages.

Every section of the kit also has a visual regression test frame: copies of each stamp and card filled with override text. When I change a core component, the frames show whether the change breaks instances teams have already placed in their files. The approach comes from the CVS team’s work. I built the frames for the rebuilt components, including the new ones.

A documentation template with one source of truth

Annotation components alone don’t tell a designer where to start. The documentation template does. It’s a Figma page split into ten sections, one per category: landmarks, page title and headings, reading order, focus order, buttons, links, images and media, inputs and form elements, errors, and dynamic content.

The designer places the experience once, inside a single component, and every section shows an instance of it. Change the design and all ten views update. Headers, footers, and other global elements get covered by an “Audited Separately” marker, because they’re documented once for the whole product, not on every screen. The setup guide keeps documentation inside the main landmark unless there’s a reason to go beyond it.

Each section has the same layout. Ready-to-fill detail cards sit on the left, and stamps for that category are staged above the design, ready to drag. On the right are the rules for that category, implementation and ARIA notes with example markup, and a checklist split by role: what the designer confirms, what the developer builds, and what QA tests. The headings section’s structure rule is the opening story in one line: don’t skip heading levels for visual reasons; use CSS for style and semantic order for structure.

One section of the documentation template on a dark canvas, titled Page Title and Headings. A row of ten tabs across the top names every section, from Landmarks to Dynamic content, with Page title and headings selected. The left column holds a heading rules card (one h1 per page, at least one h2, no h1 inside components) and empty detail cards for h1 through h4. The center holds staged h2 and Note stamps, a page metadata card, and the experience placeholder, with bands marking the header and footer as documented separately and an h1 pin at its top. The right column holds a yellow structure rule, implementation notes, a short code example, and a checklist split into Designer, Developer, and QA.(opens full size in a new tab)
One of ten sections in the documentation template. The design is placed once and appears in every section; the rules, markup, and role checklists sit beside it. Redrawn with neutral content. Select the image to view it full size in a new tab.

An audit template in the same shape

The audit template mirrors the documentation template, section for section. Each category holds the screen under review, audit cards, a recommendation card, an issue card, and the screen reader output components. A team that documented its design in the first template can audit the build in the second, using the same ten categories, plus an accessibility tree section, so an audit finding lines up with the decision it tests.

Recording what the screen reader actually said

The annotations describe what should happen. Audits need a record of what actually happens. I built a set of screen reader output components for that: a VoiceOver caption window, the VoiceOver rotor, the NonVisual Desktop Access (NVDA) Speech Viewer, and the NVDA Elements List. During an audit, they document what the screen reader really read, in the same file and the same visual language as the annotations. A finding becomes the spec beside the evidence, instead of a sentence in a ticket.

Two columns. On the left, labeled "Documentation template: what should happen," the annotated form from screen 01: the address fields in a fieldset named by the h2 heading, and Phone number and Email address marked as Input 1 and Input 2. On the right, labeled "Audit template: what actually happened," an NVDA Speech Viewer window lists what was announced: the address group, the h2, the street address field, then, highlighted in red, "heading level 2 Phone number" followed by an unnamed "edit blank." Below it, a red Issue card titled "Field label announced as a heading" cites Web Content Accessibility Guidelines (WCAG) 2.2 success criteria 1.3.1 and 4.1.2 and notes the field was documented as a label. A green Recommendation card says to build it as documented: a label tied to its input, same visual style.(opens full size in a new tab)
The documentation section and its audit section, side by side. The speech viewer records what the screen reader said, so the finding points straight back to the annotation it breaks. Illustrative example, not from a real audit. Select the image to view it full size in a new tab.

Teaching it

A system designers don’t understand doesn’t get used. The system was released and piloted with teams first. Once it was in use, I built the training and delivered 15 sessions that taught designers about accessibility and how to document it with the kit. The curriculum follows the system: setup sessions on preparing a file and using the templates, then one session for each of the ten categories, from landmarks through dynamic content. The series covered every section of the template.

The sessions are delivered activity. They show what I taught, not what changed because of it.

Office hours

When the series ended, I opened office hours in September so designers could bring their own work.

In October, a product designer brought a complex page she had annotated herself. Landmarks, headings, and reading order were already right. She needed help only where the kit gets hard: a radio group that needed a visible label, the order of the label, options, and helper text, and a message that should appear once instead of on every recalculation. Before the session ended, she was applying the same section landmarks to a second page without being asked.

It was the first layout like that since the accessibility program started in that I didn’t annotate myself. She credited the training.

She wasn’t the only one. Weeks after the series ended, another designer came back unprompted with a question about links styled as buttons.

The same October session also found problems in the standard. Her accordion question exposed two conflicts and three gaps in the design system’s own accordion spec. Office hours are where designers bring the hard cases, and they’re also where the standard learns what it got wrong.

The accordion was built the way the session settled it: a heading wrapping the button, with the caret icon inside the button, and aria-expanded and aria-controls confirmed on the trigger. Those are the same two attributes the Dynamic content card’s designer note calls for. The fix didn’t stop at her page. It went to engineering as an update to the shared accordion component, so every team that uses it gets the corrected pattern.


What changed

When designers use the system, the conversation about accessibility moves into design, where it costs the least to change. The address form is the clearest example: a semantic mistake caught in review, explained with the kit, and handed to engineering correct.

Adoption is the limit. By my count in design reviews, about 3 in 10 designers use the system consistently. The rest use it when it’s convenient, or not at all. Design teams are stretched thin, and when time is tight, optional work is the first thing dropped. That count is my own observation, not a measurement, and without consistent use there’s no clean way to measure what the system changes downstream.

Here’s what I can claim, sorted by how strong the evidence is.

EvidenceWhat I can claim
DeliveredThe system, released and piloted with teams, and 15 training sessions
UseMistakes caught in design review and handed to engineering correct, like the address form
Independent useA designer annotated a complex page correctly on her own and brought only the hard cases to office hours. Her question found five problems in the standard’s accordion spec, the accordion was built to the corrected pattern, and the fix went to engineering as a shared component update
AdoptionAbout 3 in 10 designers use it consistently, by my own count in design reviews
Sustained impactNot measured. Without consistent use, there’s no clean baseline for downstream defects

What I’d do differently

The system works where it’s used. What it lacked was a place in the process. Annotation sat beside design work as an extra step, and an extra step loses to a deadline every time.

Next time I’d build the checkpoint and the kit together. Annotation would be part of what “done” means at design review from day one, and the review would check for it the same way it checks for layout and content. Then the system doesn’t depend on a stretched designer choosing to use it, and adoption becomes something you can measure.