Accessibility Development Workflow¶
End-to-End Accessibility
Accessibility is not a testing phase. It flows through every stage of the development pipeline, from design to deployment. This page maps GE's accessibility controls at each stage.
Overview¶
Design → Spec → TDD → Implementation → Testing → Review → Deployment
↑ ↑ ↑ ↑ ↑ ↑ ↑
Alexander Anna Antje Semantic HTML axe-core Marta/ Lighthouse
Accessible a11y a11y ARIA when Screen Iwona CI
Components reqs cases needed reader a11y threshold
manual gate
Every stage has accessibility deliverables. Skipping a stage creates debt that compounds downstream.
Stage 1: Design¶
Owner: Alexander Deliverable: Accessible component specifications
Accessible Design System¶
Alexander maintains a design system where every component is accessible by default:
- Color palette with WCAG 2.2 AA contrast ratios verified
- Typography scale with minimum readable sizes
- Component states: default, hover, focus, active, disabled, error
- Focus indicators: visible, 2px minimum, 3:1 contrast ratio
- Touch targets: minimum 24x24 CSS pixels (44x44 recommended)
- Motion: respects
prefers-reduced-motion
Color Accessibility¶
| Context | Minimum Contrast | Example |
|---|---|---|
| Normal text (< 18pt) | 4.5:1 | Body copy, labels, links |
| Large text (>= 18pt or 14pt bold) | 3:1 | Headings, large buttons |
| UI components and graphics | 3:1 | Borders, icons, form controls |
| Focus indicators | 3:1 | Focus rings, outlines |
Critical rule: Never use color as the only means of conveying information. Always pair color with text, icons, or patterns.
Bad: Red = error, green = success (color only)
Good: Red + "Error: ..." text + icon, Green + "Success: ..." text + icon
Component Design Checklist¶
For every new component, Alexander specifies:
- [ ] Keyboard interaction pattern (which keys do what)
- [ ] Focus management (where focus goes on open/close/submit)
- [ ] Screen reader announcement (what is read, in what order)
- [ ] Touch target size (minimum 24x24, recommended 44x44)
- [ ] Color contrast for all states
- [ ] Behavior with
prefers-reduced-motion - [ ] Behavior with
prefers-color-scheme - [ ] Text resize behavior up to 200%
Stage 2: Specification¶
Owner: Anna Deliverable: Accessibility requirements in feature spec
Accessibility Requirements¶
Anna includes accessibility requirements in every feature specification. These are not separate from functional requirements — they are part of them.
Example specification section:
## Accessibility Requirements
### Keyboard Navigation
- Tab order follows visual layout (left-to-right, top-to-bottom)
- Modal dialog traps focus within the dialog
- Escape key closes the modal and returns focus to trigger
- Arrow keys navigate within dropdown options
### Screen Reader
- Form fields have visible labels associated via `for`/`id`
- Error messages are associated with fields via `aria-describedby`
- Dynamic content updates use `aria-live="polite"`
- Modal announces its title when opened
### Visual
- All text meets 4.5:1 contrast ratio
- Focus indicator visible on all interactive elements
- Error states use icon + text, not color alone
User Stories Include Accessibility¶
Accessibility is not a separate user story. It is part of every user story.
Bad: "As a user, I can submit the contact form."
(Separate story: "As a screen reader user, I can submit the form.")
Good: "As a user, I can submit the contact form using mouse, keyboard,
or assistive technology, with all fields labeled and errors
announced."
Stage 3: TDD — Test-Driven Development¶
Owner: Antje Deliverable: Accessibility test cases written before implementation
a11y Test Cases¶
Antje writes test cases that verify accessibility before the implementing agent writes a single line of component code.
Categories of accessibility tests:
Structural Tests¶
test('form fields have associated labels', () => {
render(<ContactForm />);
const emailInput = screen.getByLabelText('Email address');
expect(emailInput).toBeInTheDocument();
expect(emailInput).toHaveAttribute('type', 'email');
expect(emailInput).toHaveAttribute('aria-required', 'true');
});
test('heading hierarchy is correct', () => {
render(<Page />);
const headings = screen.getAllByRole('heading');
// h1 appears exactly once
const h1s = headings.filter(h => h.tagName === 'H1');
expect(h1s).toHaveLength(1);
});
Keyboard Navigation Tests¶
test('modal traps focus', () => {
render(<ModalDialog />);
const trigger = screen.getByRole('button', { name: 'Open dialog' });
fireEvent.click(trigger);
// Focus moves to dialog
const dialog = screen.getByRole('dialog');
expect(dialog).toHaveFocus();
// Tab stays within dialog
userEvent.tab();
expect(within(dialog).getByRole('button', { name: 'Cancel' })).toHaveFocus();
// Escape closes and returns focus
userEvent.keyboard('{Escape}');
expect(dialog).not.toBeInTheDocument();
expect(trigger).toHaveFocus();
});
test('dropdown navigable with arrow keys', () => {
render(<Dropdown options={['Option 1', 'Option 2', 'Option 3']} />);
const trigger = screen.getByRole('combobox');
fireEvent.keyDown(trigger, { key: 'ArrowDown' });
expect(screen.getByRole('option', { name: 'Option 1' })).toHaveFocus();
fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
expect(screen.getByRole('option', { name: 'Option 2' })).toHaveFocus();
});
Screen Reader Announcement Tests¶
test('error message announced to screen readers', () => {
render(<ContactForm />);
const submitButton = screen.getByRole('button', { name: 'Submit' });
fireEvent.click(submitButton);
const errorMessage = screen.getByRole('alert');
expect(errorMessage).toHaveTextContent('Please fill in required fields');
});
test('live region announces status updates', () => {
render(<SearchResults />);
const liveRegion = screen.getByRole('status');
// After search completes
expect(liveRegion).toHaveTextContent('5 results found');
});
Automated a11y Tests¶
import { axe, toHaveNoViolations } from 'jest-axe';
test('page has no accessibility violations', async () => {
const { container } = render(<Page />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Stage 4: Implementation¶
Owner: Floris (architecture), Floor (components) Deliverable: Accessible, semantic HTML with ARIA when needed
Semantic HTML First¶
The first rule of ARIA: do not use ARIA if native HTML can do it.
<!-- Bad: ARIA role on a div -->
<div role="button" tabindex="0" onclick="submit()">Submit</div>
<!-- Good: native button element -->
<button type="submit">Submit</button>
<!-- Bad: div soup with ARIA landmarks -->
<div role="navigation">
<div role="list">
<div role="listitem"><a href="/">Home</a></div>
</div>
</div>
<!-- Good: semantic HTML -->
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
Semantic Element Reference¶
| Purpose | Use | Not |
|---|---|---|
| Page regions | <header>, <nav>, <main>, <footer>, <aside> |
<div> with ARIA roles |
| Headings | <h1> through <h6> in order |
<div class="heading"> |
| Lists | <ul>, <ol>, <li> |
<div> with bullet styling |
| Tables | <table>, <th>, <td> with scope |
<div> grid with CSS |
| Forms | <form>, <fieldset>, <legend>, <label> |
<div> with click handlers |
| Buttons | <button> |
<div onclick>, <a href="#"> |
| Links | <a href="..."> |
<span onclick> |
| Emphasis | <strong>, <em> |
<span class="bold"> |
When to Use ARIA¶
ARIA is appropriate when:
- Custom widgets have no native HTML equivalent (tabs, tree views, comboboxes)
- Dynamic content updates need to be announced (
aria-live) - Relationships between elements need explicit declaration (
aria-describedby) - States need to be communicated (
aria-expanded,aria-selected,aria-checked) - Error messages need association with inputs (
aria-errormessage,aria-invalid)
ARIA Patterns for Common Widgets¶
Tabs:
<div role="tablist" aria-label="Account settings">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Security
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- Profile content -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- Security content -->
</div>
Disclosure (expand/collapse):
<button aria-expanded="false" aria-controls="details-content">
Show details
</button>
<div id="details-content" hidden>
<!-- Expandable content -->
</div>
Alert/Status:
<!-- For urgent, important messages -->
<div role="alert">
Payment failed. Please check your card details.
</div>
<!-- For non-urgent status updates -->
<div role="status" aria-live="polite">
3 items added to cart.
</div>
Focus Management¶
Focus management is critical in single-page applications where content changes without a page reload.
Rules:
- When a modal opens, focus moves to the modal (first focusable element or the dialog itself)
- When a modal closes, focus returns to the element that opened it
- When content is deleted, focus moves to a logical next element
- When a page section loads dynamically, focus stays where it was (use
aria-liveto announce changes) - Never move focus unexpectedly
- All focusable elements have a visible focus indicator
Forms¶
<form>
<div>
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-required="true"
aria-describedby="email-help email-error"
aria-invalid="true"
/>
<p id="email-help">We will only use this to send your receipt.</p>
<p id="email-error" role="alert">Please enter a valid email address.</p>
</div>
<fieldset>
<legend>Notification preferences</legend>
<label>
<input type="checkbox" name="notify-email" />
Email notifications
</label>
<label>
<input type="checkbox" name="notify-sms" />
SMS notifications
</label>
</fieldset>
<button type="submit">Submit order</button>
</form>
Form rules:
- Every input has a visible
<label>associated viafor/id - Never use
placeholderas a label substitute - Group related inputs with
<fieldset>and<legend> - Associate error messages with inputs via
aria-describedby - Mark invalid fields with
aria-invalid="true" - Describe errors in text, not just color
Images¶
<!-- Informative image: alt text describes content -->
<img src="chart.png" alt="Revenue grew 40% from Q1 to Q4 2025" />
<!-- Decorative image: empty alt -->
<img src="divider.png" alt="" />
<!-- Complex image: detailed description -->
<figure>
<img src="architecture.png" alt="System architecture diagram"
aria-describedby="arch-desc" />
<figcaption id="arch-desc">
Three-layer architecture: client browser connects to API gateway
via TLS, which routes to internal services via mTLS. Services
access PostgreSQL database with role-based credentials.
</figcaption>
</figure>
<!-- Icon with text: icon is decorative -->
<button>
<svg aria-hidden="true"><!-- icon SVG --></svg>
Delete item
</button>
<!-- Icon-only button: needs accessible name -->
<button aria-label="Close dialog">
<svg aria-hidden="true"><!-- X icon SVG --></svg>
</button>
Stage 5: Testing¶
Owner: Antje (test cases), implementing agent (execution) Deliverable: Accessibility verified through automated + manual testing
Automated Testing¶
Automated tools catch approximately 30-40% of accessibility issues. They are necessary but not sufficient.
Tools in the GE pipeline:
| Tool | What It Catches | Integration Point |
|---|---|---|
| axe-core | WCAG violations in rendered DOM | Unit tests (jest-axe) |
| Lighthouse | Page-level accessibility score | CI/CD pipeline |
| eslint-plugin-jsx-a11y | Accessibility issues in JSX at write time | Linting |
| Pa11y | Page-level WCAG conformance | CI/CD pipeline |
axe-core in unit tests:
import { axe } from 'jest-axe';
test('component has no a11y violations', async () => {
const { container } = render(<Component />);
expect(await axe(container)).toHaveNoViolations();
});
Lighthouse CI threshold:
lighthouse:
accessibility:
score: 95 # Minimum score to pass
# 100 is the target, 95 allows for edge cases
# that require manual verification
Manual Testing¶
Automated tools miss 60-70% of accessibility issues. Manual testing is required.
Screen reader testing matrix:
| Screen Reader | Browser | OS | Priority |
|---|---|---|---|
| NVDA | Firefox | Windows | Primary |
| VoiceOver | Safari | macOS/iOS | Primary |
| TalkBack | Chrome | Android | Secondary |
| JAWS | Chrome | Windows | Reference |
Manual testing checklist:
- [ ] Keyboard only: Complete all tasks without a mouse
- [ ] Tab order: Logical and follows visual layout
- [ ] Focus visibility: Every focused element has a visible indicator
- [ ] Screen reader: Content is announced in logical order
- [ ] Screen reader: Interactive elements announce role, name, and state
- [ ] Screen reader: Dynamic content changes are announced
- [ ] Zoom 200%: All content remains visible and functional
- [ ] High contrast mode: Content visible in Windows High Contrast Mode
- [ ] Reduced motion: Animations respect
prefers-reduced-motion - [ ] No mouse: All hover-only information is available via focus or other means
Stage 6: Code Review¶
Owner: Marta, Iwona Deliverable: Accessibility verified in merge gate
Accessibility Review Checklist¶
Marta and Iwona check accessibility as part of every code review:
- [ ] Semantic HTML used (no
<div>as button, no<span>as link) - [ ] ARIA used correctly and only when necessary
- [ ] Images have appropriate alt text
- [ ] Forms have labels, fieldsets, and error handling
- [ ] New components have keyboard interaction
- [ ] Focus management handles dynamic content
- [ ] Color is not the only means of conveying information
- [ ] axe-core tests included for new components
- [ ] No inline styles that break user stylesheets
Merge blocked if:
- axe-core tests fail
- Lighthouse accessibility score below threshold
- New component lacks keyboard support
- Form fields lack labels
Stage 7: Deployment¶
Owner: CI/CD pipeline Deliverable: Accessibility gates pass in production-like environment
Lighthouse CI¶
Lighthouse accessibility audit runs in the deployment pipeline against the staging environment.
Gate configuration:
assertions:
accessibility:
- minScore: 0.95
level: error # Blocks deployment
categories:accessibility:
- minScore: 0.90
level: warn # Warns but allows deployment
Post-Deployment Monitoring¶
- Lighthouse scheduled scans weekly on production
- Regression alerts if score drops below threshold
- Accessibility audit as part of quarterly review
Component-Level Accessibility Checklist¶
This checklist flows through the pipeline. Each stage adds verification.
| Check | Design | Spec | TDD | Impl | Test | Review |
|---|---|---|---|---|---|---|
| Contrast ratios | x | x | ||||
| Touch targets | x | x | x | |||
| Keyboard interaction | x | x | x | x | x | x |
| Focus management | x | x | x | x | x | x |
| Screen reader labels | x | x | x | x | x | |
| Semantic HTML | x | x | x | |||
| ARIA correctness | x | x | x | |||
| Error handling | x | x | x | x | x | |
| Motion/animation | x | x | x | |||
| Responsive/zoom | x | x | x | |||
| axe-core clean | x | x | x |
Further Reading¶
- Accessibility-First Philosophy — Why accessibility comes first
- Accessibility Pitfalls — Common mistakes to avoid
- WebAIM WCAG 2 Checklist — External reference
- WAI-ARIA Authoring Practices — Widget interaction patterns