We promise that adaptive learning works for every learner. That promise has a quiet corollary: if you can't read the screen, can't use a mouse, can't tolerate motion, can't see the focus ring, or rely on a screen reader, the product has to work for you anyway. Otherwise we're saying "every learner who matches a particular profile of vision, motor function, and cognitive style," which is a much smaller promise.
Last week we did a fleet-wide accessibility audit and remediation across the entire AccelaStudy AI surface area. This post is what we did, why we did it the way we did, and what the numbers look like now. If you're a learner who relies on assistive technology, the short version is: the entire product passes our deterministic accessibility audit, every interactive control has an accessible name, every form input is labelled, every modal traps focus correctly, and every animation respects your prefers-reduced-motion setting. If you're an engineer or a product person curious about how, the rest of this post is the mechanics.
What "fully accessible" means to us
The accessibility standard we hold ourselves to is WCAG 2.1 Level AA. That's the bar enterprise procurement teams check when they evaluate AccelaStudy AI for school districts and Fortune 500 learning programs. It's also the bar most users experience as "the product just works." WCAG 2.1 AA defines about fifty success criteria covering everything from alt-text on images to keyboard navigation to color contrast to focus indicators to motion preferences. Meeting it is not a checklist item; it's a property the codebase has to keep.
Here's the framing we work from:
| State a codebase can be in | What it requires |
|---|---|
| Aspirationally accessible | Some components are right; some aren't; nobody knows which |
| Audited accessible | A scan happened once, the findings got fixed |
| Continuously accessible | Every commit is gated by a deterministic check; regressions can't merge |
We were the second; we're now the third. The transition needed two things: an audit that runs in CI and refuses to ship broken accessibility, and a one-time mass remediation to get to zero before the gate started enforcing.
The audit
We built a deterministic Python script that scans every TSX, JSX, HTML, and CSS file in fifty-six AVIAN repositories. The script implements fifteen rules, each mapping to one or more WCAG 2.1 success criteria:
| Rule | What it catches |
|---|---|
| Image without alt | <img> tags missing alt="" (decorative) or descriptive alt |
| Informational SVG without ARIA | Standalone SVG glyphs not marked with role/label or aria-hidden |
| Canvas without text alternative | Knowledge Map and similar visualizations needing a text summary |
| Input without accessible name | Form fields screen readers can't announce |
| Multiple headings of the same rank | Pages declaring more than one <h1> |
| Missing main landmark | Layouts without a <main> region for "skip to content" |
| Missing skip link | Layouts without a "Skip to main content" anchor |
| HTML missing lang | Pages without lang="en" (or appropriate locale) |
| Generic link text | "Click here" / "learn more" without context |
| Clickable non-interactive element | <div onClick> without keyboard support |
| Aria-hidden focusable element | Hidden elements still in the tab order |
| Positive tabIndex | Tab-order overrides that confuse keyboard users |
| Outline:none without replacement | Focus rings invisibly removed |
| No reduced-motion guard | Animations not respecting prefers-reduced-motion |
| Missing sr-only utility | Repos lacking the visually-hidden screen-reader utility |
The script auto-discovers UI repositories, skips test fixtures and build artifacts, and emits two reports: a Markdown summary you can read, and a JSON file CI can diff. In its default mode, it exits with a non-zero status when any HIGH or CRITICAL finding remains. That's the gate.
What we found
The first run of the script reported 414 HIGH-severity findings, 312 MEDIUM-severity findings, and zero CRITICAL. The HIGH findings clustered into two patterns:
- 2,235 form inputs in the cloud-console simulator (used by labs across forty-five certifications) had a visible label next to them but no programmatic linkage. Sighted users saw the label; screen readers didn't.
- Smaller pockets of issues across the consumer apps, internal tools, and marketing sites: missing aria-labels on icon-only buttons, modal backdrops without
role="presentation", decorative SVGs not markedaria-hidden, focus rings stripped without replacement.
Both patterns were obviously fixable. The first needed a codemod. The second needed a focused manual pass.
How we fixed it
We worked in nine waves over a single day:
The codemods did the heavy lifting. A 200-line Python script walked the JSX of every console-sim dashboard, found adjacent label/input pairs, generated a stable id from each input's test identifier, and rewrote both elements with the right htmlFor/id linkage. It applied 2,235 fixes in eight seconds. A second codemod demoted 594 page-secondary <h1> elements to <h2> across 241 files. A third added aria-label to 50 inputs that had a test identifier but no preceding sibling label.
The non-mechanical work — modal focus management, custom widget keyboard patterns, decorative-SVG marking, focus-ring restoration — went to focused review passes across the apps and the tools fleet.
What's in the codebase now
A snapshot of the AccelaStudy AI accessibility footprint after the audit:
| Metric | Count |
|---|---|
| UI-bearing repositories | 56 |
| TSX/JSX source files | 2,185 |
Native <button> elements | 2,527 |
| Form inputs (input / select / textarea) | 3,486 |
| Total ARIA attribute uses | 3,019 |
Explicit aria-label uses | 1,530 |
aria-hidden uses (decorative elements) | 752 |
role= uses | 1,001 |
role="dialog" (modals) | 109 |
role="img" (canvas/SVG with text alternative) | 112 |
role="button" (custom interactive elements) | 101 |
tabIndex uses | 150 |
onKeyDown keyboard handlers | 208 |
Tailwind focus-visible: classes | 102 |
| Skip-to-main-content references | 200+ |
| HIGH findings before remediation | 414 |
| HIGH findings after remediation | 0 |
| Findings at every severity, after | 0 |
| Total fixes shipped in one day | 3,317 |
The numbers worth lingering on are the 2,527 native <button> elements and the 3,486 labelled form inputs. Native HTML semantics are the foundation of accessibility; ARIA is the extension. We lean heavily on native semantics, then use ARIA where the visual design exceeds what plain HTML can express. That's the right ratio.
What it means for you
If you're a learner using AccelaStudy AI:
- Screen reader. Every button, link, input, and interactive control announces with a meaningful name. The Knowledge Map visualization has a text summary describing your concepts mastered, in progress, and not started. The Behavioral Rings (precision, fluency, confidence, curiosity) announce their current values. The exam timer announces minutes remaining politely, and warnings before time runs out are announced assertively.
- Keyboard only. Every interactive control is reachable by Tab. Activities that involve drag-and-drop (procedural step sequencing, evidence categorization) have a full keyboard mode: Space to grab, arrow keys to move, Enter to drop, Escape to cancel. The activity announces position changes through a live region. The radio group on the subscribe page cycles with arrow keys, per the WAI-ARIA radio pattern.
- Switch device or voice control. Every interactive element has a name, role, and tab-stop. Voice control software can target controls by their accessible names. Switch-scanning software can step through the interactive surface in a logical order.
- Magnification. Layouts use rem-based typography that scales with browser font-size settings. The shell adapts to viewport widths down to 320px without horizontal overflow. Tap targets meet WCAG's 44×44 pixel minimum on every interactive control we audited.
- Reduced motion. Every CSS file in the platform respects
prefers-reduced-motion: reduce. Animations stop, transitions shorten, parallax disables. The fireworks overlay that celebrates a passing exam stays celebratory but stops being a problem if your settings request stillness. - Cognitive accessibility. Forms group related fields with
<fieldset>and<legend>. Errors announce via live regions instead of disappearing into red text. The activity pace adapts to your performance, and the timer is pausable in TimedRecall and PracticeExam where appropriate.
What stays continuous
The work this week was finite. The discipline is continuous. Three things keep us at zero findings:
- The audit script lives in CI. It runs on every pull request. A commit that introduces a missing label, a stripped focus ring, or a clickable div without keyboard support fails the check.
- The audit spec is a committed document. It defines the rules, the severities, and the fix patterns. Updates to the spec and the script ship in the same commit, so the audit a developer runs is always the audit CI runs.
- The codemods are inventory. The Python scripts that did the mass fixes live alongside the audit. The next time a similar pattern surfaces — a new library, a new dashboard, a new activity format — the codemod runs again instead of two thousand manual edits.
We also have an axe-core Playwright sweep across thirty-four routes that runs on the consumer app, catching color-contrast and computed-DOM-state issues the static audit can't. Manual screen-reader passes happen at release boundaries. The combination — deterministic in CI, broader Playwright on integration, manual on release — is what "fully accessible" actually looks like in practice.
What we owe you
If you're a learner using assistive technology and you hit a barrier we missed, we want to know. Email accessibility@accelastudy.com or use the bug-report button in the app (it's keyboard-reachable and labelled "Report a bug"). Every report routes to our accessibility queue and we triage them as gating defects, not nice-to-haves. The audit script catches the mechanical eighty percent. The remaining twenty percent — the contextual stuff a real human doing a real task notices — is exactly the kind of feedback that makes the platform better.
The promise is "every learner." We mean it.