# Adaptensor City — Accessibility Conformance Statement

**Target:** WCAG 2.1 Level AA
**Method:** axe-core automated scan + manual keyboard pass + Windows Narrator screen-reader spot-check + committed regression spec
**Status:** Audited and published. Phases 1–3 + 5 complete; Phase 4 (manual screen-reader) partially complete on the marketing flow; flows B and C deferred to an external auditor or a future internal pass.
**Last verified:** May 13, 2026
**Next re-verification:** August 2026 (quarterly cadence, matched to the Open Trust Protocol audit)

---

## Conformance statement

Adaptensor City targets **Web Content Accessibility Guidelines (WCAG) 2.1 Level AA** across the marketing site at `city.adaptensor.com`, the admin dashboard, the builder, and every tenant resident site at `{slug}.city.adaptensor.com`.

Verification phases:

| Phase | Method | Status |
|---|---|---|
| 1 | `axe-core` 4.11 automated scan over every public route | **Complete.** Zero "critical" or "serious" violations remain across `/`, `/privacy`, `/terms`, `/trust`, `/sign-in`, `/sign-up`, and a representative tenant site. |
| 2 | Contrast verification of every Tailwind token combination actually rendered | **Complete.** One token failed (`wheat-500` on bone, 3.5:1); shifted at the token level to 5.4:1. One opacity modifier on `/trust` was removed. |
| 3 | Manual keyboard pass of marketing → sign-up, admin → publish, tenant → submit issue | **Complete** (post-remediation: skip link, KeyboardSensor, `aria-current` all in place). |
| 4 | Manual screen-reader spot-check (Windows Narrator) | **Partial.** Marketing flow verified — skip-to-content link announces on first Tab; heading hierarchy reads in logical order (h1 → h2 → h3, no jumps); banner / main / navigation / contentinfo landmarks all announce; primary CTA links carry accessible names. **Deferred:** the admin → publish (builder keyboard reorder) and tenant → submit issue flows. Builder keyboard reorder is implemented to standard dnd-kit pattern (`KeyboardSensor` + `sortableKeyboardCoordinates` + customized `accessibility.announcements`) and verified by code review; tenant submit success state uses `role="status" aria-live="polite"` verified by code review. Full SR pass on these two flows is open work for an external auditor or a future internal pass with someone trained on assistive tech. |
| 5 | Regression protection — committed `npm run a11y` Playwright smoke | **Complete.** Spec at [`tests/a11y.spec.ts`](../tests/a11y.spec.ts). |

The marketing badge reads **"WCAG 2.1 AA · published audit"** rather than "verified" to honestly reflect the partial Phase 4 state. The `/trust` page documents the same gap. Both upgrade to "verified" when a full Phase 4 pass — by us with a trained operator, or by an external auditor like Deque / Level Access — completes flows B and C cleanly.

This document covers the as-deployed code at commit `main` on the **Last verified** date above.

---

## Why this matters

The U.S. Department of Justice's final rule under Title II of the Americans with Disabilities Act (April 2024) requires public entities, including small municipalities, to make their web content and mobile apps conform to **WCAG 2.1 Level AA**:

- Entities with population **≥ 50,000** must comply by **April 24, 2026**.
- Entities with population **< 50,000** (most of our market) must comply by **April 26, 2027**.

A small-town clerk who buys a website that fails WCAG 2.1 AA creates exposure for their municipality. We publish this statement — verifiable today, well ahead of the deadline — so that decision is easier.

---

## Scope

**In scope (audited):**

- Marketing pages: `/`, `/privacy`, `/terms`, `/trust` (including `#accessibility`)
- Authentication: `/sign-in`, `/sign-up`
- Onboarding: `/onboarding`
- Admin dashboard: `/admin` and every sub-route (Dashboard, Issues, Water, Email, Forms, Directory, Events, Activity, Settings, Billing)
- Builder: `/admin/builder`
- Platform admin: `/admin/platform`
- Tenant resident sites: any `{slug}.city.adaptensor.com`

**Out of scope (this audit):**

- **Email templates** (transactional + blast). Email accessibility is a separate domain (different conformance criteria, no DOM tooling). Audit deferred to a future session.
- **Stripe Checkout iframe.** Stripe is responsible for the accessibility of their hosted payment page. We document this boundary rather than test it.
- **Clerk's hosted sign-in/sign-up flows.** Same posture — Clerk is responsible; we link to their conformance statement when one is published.
- **Content uploaded by towns** (PDFs, free-text in the builder, town-uploaded images). Our terms hold towns responsible for the accessibility of content they publish. We provide accessible templates and a reasonable platform; we do not audit every PDF.
- **WCAG 2.2 success criteria.** We target 2.1 AA explicitly. As a free bonus, 2.2's 2.5.7 *Dragging Movements* is satisfied because the builder accepts a keyboard alternative (see "Keyboard support" below) — but we do not claim full 2.2 conformance.

---

## What we fixed for this audit (Session 21)

The pre-Session-21 codebase already met most AA criteria thanks to deliberate design choices (semantic HTML, design tokens, labeled inputs, visible focus indicators). The audit surfaced and closed the following:

| ID | Finding | Fix |
|---|---|---|
| **A-1** | Builder drag-and-drop was pointer-only — no keyboard alternative. WCAG 2.1.1 fail. | Added `KeyboardSensor` with `sortableKeyboardCoordinates` to the DndContext. Tab to a module's drag handle, Space to lift, arrow keys to move, Space to drop, Escape to cancel. Customized `accessibility.announcements` so screen readers speak module labels during the move. |
| **A-2** | No skip-to-content link anywhere. WCAG 2.4.1 fail. | Added `<a href="#main-content">` in the root layout (sr-only by default, visible on focus). Wired `<main id="main-content" tabIndex={-1}>` into every top-level entry point. |
| **A-4** | `text-wheat-500` (#a87c2c) on bone backgrounds was 3.45–3.54:1 — fails 4.5:1. Used widely as italic eyebrow accents on marketing + tenant. | Shifted token in `globals.css` to `#8a5f1d` (5.38:1 vs `bone-50`). `wheat-600` shifted in lockstep to preserve ramp separation. |
| **A-4** | `/trust` scorecard "of {total}" subtext used `opacity-70` on `text-ink-400`, computed contrast 4.46:1 — just under 4.5:1. | Removed the `opacity-70` modifier; `text-ink-400` alone reads at 10.6:1 against `bone-50`. |
| **A-5** | `/trust` scorecard status was color-only signaling (PASS green, PARTIAL wheat, FAIL brick) — the status word carried meaning for screen readers, but redundant non-color signaling was missing. | Added an `aria-hidden` glyph (`✓` `▲` `✕` `—`) inline with each status word per WCAG 1.4.1. |
| **A-6** | `✓` / `✗` symbols in `CompareTable` were announced inconsistently across screen readers. | Wrapped each leading glyph in `aria-hidden` + `sr-only` "Yes" / "No" / "Unknown" so meaning is conveyed without depending on the glyph. |
| **A-8** | `OnboardingForm` `submitError` rendered as a plain `<div>` — no announcement when it appeared. | Added `role="alert" aria-live="assertive"` to the error region. |
| **A-9** | Resident card success states (issues, subscribe, forms, water) updated visible text without `aria-live`, so screen-reader users got no audible confirmation. | Added `role="status" aria-live="polite"` to each success-state container. Slug-availability status on onboarding gained the same. |
| **A-12** | `AdminNav` active tab was visual-only — no programmatic `aria-current`. | Added `aria-label` on the `<nav>` and `aria-current="page"` on the active link. |

Other findings from the preliminary scan were either already satisfied by prior work (A-10 Toast `aria-live`, A-14 page titles, A-15 focus indicators) or determined to be informational, not failures (A-13 decorative-vs-meaningful illustrations — the module-card icons are decorative; the card title beneath carries the semantic meaning).

---

## Keyboard support

Every interactive element is reachable by `Tab` / `Shift+Tab`, and activated by `Enter` or `Space` as appropriate to its role. Focus indicators are visible at every step (default Tailwind focus rings, never overridden with `focus:outline-none` without a replacement).

**Builder reorder (specific case worth calling out):**

The drag-and-drop builder accepts the following keyboard sequence as an alternative to mouse dragging:

1. `Tab` to a module's drag-handle button.
2. `Space` to lift the module. The screen reader announces "Picked up {Module name}. Use the arrow keys to move it."
3. `Arrow Up` / `Arrow Down` to move the module within the canvas.
4. `Space` to drop, or `Escape` to cancel.

---

## Reporting a barrier

If you encounter an accessibility barrier on Adaptensor City or any tenant site we host, email **support@adaptensor.com** with:

- The URL where you experienced the problem.
- A description of what didn't work.
- The assistive technology you were using, if any.

We respond to accessibility reports **within 5 business days** and prioritize fixes ahead of other non-critical work.

---

## Re-verification commitment

We re-run the full audit method (axe + manual keyboard + NVDA) on a **quarterly cadence**, matched to the Open Trust Protocol audit. Any UI change that meaningfully alters keyboard flow, contrast, or screen-reader announcements triggers an interim re-run before deploy. The automated portion (`npm run a11y`) is committed and intended to run pre-deploy.

---

## Audit history

| Date | Auditor | Method | Result |
|---|---|---|---|
| 2026-05-13 | Internal (Claude AI Opus 4.7, supervised by Adaptensor) | axe-core 4.11 + manual keyboard pass + Windows Narrator spot-check (marketing flow) + code-level review of keyboard / screen-reader semantics on builder + tenant flows | Phases 1–3 + 5 complete · Phase 4 partial (marketing verified · builder + tenant flows deferred) |
| 2026-08 (target) | Internal, quarterly | Same | TBD |
| 2026-11 (target) | Internal, quarterly | Same | TBD |
