Accessibility in Ashlar is structured evidence, not a marketing claim. Every stable component ships an evidence packet — machine-readable WCAG mapping, automated test results, manual keyboard notes, manual screen-reader notes, and known limitations — that auditors, AI tools, and consumers can query.
This document specifies the engineering target, the evidence packet schema, the test matrix, and the stable-component gates.
WCAG 2.2 AA where practical.
Legal baselines are documented separately:
- Section 508 (federal procurement and federal agencies) — incorporates WCAG 2.0 AA by reference.
- ADA Title II (state and local government, public-facing web/mobile) — references WCAG 2.1 AA. As of the current ADA.gov fact sheet reviewed on April 29, 2026, compliance dates are April 26, 2027 for governments with total population of 50,000 or more, and April 26, 2028 for governments below 50,000 and special district governments.
- European Accessibility Act (private sector serving EU consumers, enforced June 2025) — references WCAG 2.1 AA via EN 301 549.
Targeting 2.2 future-proofs the system without overpromising compliance for any single regime. Compliance language in docs explicitly disclaims that component-level support guarantees application-level compliance.
Required language in component docs:
Ashlar components are designed and tested to support accessible implementation. Accessibility conformance for the final service depends on correct use, content, configuration, and integration.
Forbidden language:
This component makes your application 508 compliant. Ashlar guarantees WCAG 2.2 AA compliance.
Every stable component ships *.evidence.json:
{
"$schema": "https://ashlar.dev/schemas/evidence.schema.json",
"component": "dialog",
"version": "1.0.0",
"stability": "stable",
"lastReviewed": "2026-04-24",
"reviewer": "blen-a11y-team",
"wcag": [
{
"criterion": "1.4.3",
"level": "AA",
"title": "Contrast (Minimum)",
"status": "pass",
"evidence": "tests/contrast.spec.ts",
"notes": "All text/background pairs meet 4.5:1 against default theme; agency themes validated by `ashlar theme validate`."
},
{
"criterion": "2.1.1",
"level": "A",
"title": "Keyboard",
"status": "pass",
"evidence": "tests/keyboard.spec.ts"
},
{
"criterion": "2.4.3",
"level": "A",
"title": "Focus Order",
"status": "pass",
"evidence": "tests/focus-order.spec.ts"
},
{
"criterion": "4.1.2",
"level": "A",
"title": "Name, Role, Value",
"status": "pass",
"evidence": "tests/aria.spec.ts manual-screen-reader.md"
}
],
"baselineTests": [
{
"source": "Section 508 ICT Testing Baseline for Web",
"test": "Keyboard Accessible",
"status": "pass",
"evidence": "tests/keyboard.spec.ts manual-screen-reader.md"
}
],
"manualTests": [
{
"tech": "NVDA",
"version": "2025.4",
"browser": "Firefox",
"browserVersion": "145",
"os": "Windows 11",
"date": "2026-04-24",
"tester": "ke@blencorp.com",
"result": "pass",
"evidence": "manual/dialog-nvda-firefox.md",
"notes": "All states announced correctly; modal title read on open; close button labeled."
},
{
"tech": "VoiceOver",
"browser": "Safari",
"browserVersion": "26",
"os": "macOS 26",
"date": "2026-04-24",
"tester": "ke@blencorp.com",
"result": "pass-with-note",
"evidence": "manual/dialog-voiceover-safari.md",
"notes": "iOS VoiceOver requires double-tap to dismiss when popover is anchored to scrolling parent; documented limitation."
},
{
"tech": "JAWS",
"version": "2025",
"browser": "Chrome",
"browserVersion": "138",
"os": "Windows 11",
"date": "2026-04-23",
"tester": "ke@blencorp.com",
"result": "pass"
}
],
"automatedResults": {
"axe": {
"violations": 0,
"passes": 12,
"lastRun": "2026-04-26",
"axeVersion": "4.11.0"
},
"playwright": {
"tests": 18,
"passing": 18,
"lastRun": "2026-04-26"
}
},
"knownLimitations": [
{
"id": "ios-voiceover-anchored-popover",
"description": "On iOS VoiceOver, popovers anchored to a scrolling parent may require double-tap to dismiss.",
"impact": "minor",
"workaround": "Use sticky-positioned trigger or document this behavior in pattern docs.",
"wcag": null,
"trackingIssue": "https://github.com/blencorp/ashlar/issues/142"
}
],
"antiPatternMappings": [
{ "antiPatternId": "icon-only-needs-label", "preventsViolation": "4.1.2" },
{ "antiPatternId": "remove-focus-outline", "preventsViolation": "2.4.7" }
]
}Manual keyboard and screen-reader review is captured before publication as a separate manual-evidence artifact, then folded into a proposed packet with ashlar evidence review. ashlar evidence prepare-stable <component> --fixture <path> --output <dir> generates the non-mutating reviewer bundle for the common path: automated evidence, proposed packet, self-contained REVIEW.html fixture harness, manual evidence worksheet, keyboard transcript worksheet, screen-reader transcript worksheet, a prefilled ISSUE.md for the stable-evidence review request, and a README with the next commands. ashlar evidence prepare-stable-all --output <dir> batches that same bundle for every markup primitive capsule by default and writes an intake INDEX.md so reviewers can pick a capsule without maintainers hand-assembling files. ashlar evidence review-status <component> --review-dir <dir> reads that bundle, validates the proposed packet, manual worksheet, and transcripts, reports remaining placeholders or blocked results, and simulates review plus graduation in memory without writing reviewed or stable evidence files. ashlar evidence manual-template <component> --output <path> generates a schema-backed starter file from the component's WCAG and ICT Baseline mappings; its placeholders are known-issue / blocked until a reviewer replaces them with real observations. ashlar evidence transcript-template <component> --type keyboard|screen-reader --output <path> generates the matching manual-transcript.schema.json starter, and ashlar evidence transcript-validate <component> --transcript <path> [--type keyboard|screen-reader] validates completed transcripts against schema, type, and component/version before they are referenced from the manual review. Manual test references that point to local files must resolve to manual-transcript.schema.json JSON artifacts, so the stable gate validates the transcript type, component/version, result, environment, steps, and placeholder-free content before graduation. A reviewed packet can be marked stable-evidence with ashlar evidence graduate only if the full evidence gate passes. ashlar evidence publish <component> --evidence-file <path> --signing-key <path> --key-id <id> --output <receipt> then writes the graduated packet into a local registry source, copies referenced local transcript files into the capsule under evidence/, rewrites the packet to point at those capsule-local files, rebuilds the capsule manifest, signs it with a trusted local Ed25519 key, updates the registry index capsule hash, and emits a publication receipt. Existing capsule evidence paths are reused only when the file content matches; a different transcript at the same path fails before signing or registry writes. This keeps generated or human review artifacts out of signed registry evidence until a maintainer has reviewed and re-signed the capsule, and it gives external review records a concrete artifact to prove the reviewed packet was actually published.
For the stable gate, manual review must include both keyboard evidence and screen-reader evidence. The keyboard test must record manualTests[].tech as keyboard review and point manualTests[].evidence to a local schema-backed keyboard transcript JSON artifact or an HTTPS evidence URL. The screen-reader test must record the assistive technology in manualTests[].tech as a real screen reader, such as NVDA, JAWS, VoiceOver, TalkBack, Narrator, Orca, ChromeVox, or "screen reader", and point manualTests[].evidence to a local schema-backed screen-reader transcript JSON artifact or an HTTPS evidence URL. Keyboard-only evidence does not satisfy the screen-reader requirement; screen-reader notes do not substitute for keyboard evidence. A result without an evidence reference, or a TODO / TBD / placeholder evidence string, does not satisfy either requirement. During ashlar evidence graduate and proposed-packet checks, local evidence references must resolve to files from the project root. Once published into the registry, local evidence references resolve from the capsule directory so transcript files are part of the signed capsule. External references must be HTTPS URLs.
{
"$schema": "https://ashlar.dev/schemas/manual-evidence.schema.json",
"schemaVersion": "1.0",
"component": "button",
"version": "0.0.1",
"reviewedAt": "2026-05-05T14:00:00.000Z",
"reviewer": "a11y-reviewer@example.gov",
"wcag": [
{
"criterion": "2.4.7",
"status": "pass",
"evidence": "manual/button-keyboard-review.md#focus-visible"
}
],
"baselineTests": [
{
"source": "Section 508 ICT Testing Baseline for Web",
"test": "Keyboard Accessible",
"status": "pass",
"evidence": "manual/button-keyboard-review.md"
}
],
"manualTests": [
{
"tech": "Keyboard",
"browser": "Firefox",
"browserVersion": "145",
"os": "Windows 11",
"date": "2026-05-05",
"tester": "a11y-reviewer@example.gov",
"result": "pass",
"evidence": "manual/button-keyboard-review.json#keyboard-firefox",
"notes": "Tab, Shift+Tab, Enter, Space, disabled state, and focus visibility passed."
},
{
"tech": "NVDA",
"version": "2025.4",
"browser": "Firefox",
"browserVersion": "145",
"os": "Windows 11",
"date": "2026-05-05",
"tester": "a11y-reviewer@example.gov",
"result": "pass",
"evidence": "manual/button-screen-reader-review.json#nvda-firefox",
"notes": "Focus, label, role, and activation were announced as expected."
}
],
"knownLimitations": []
}Required for every component (proposal/experimental status onwards):
- TypeScript typecheck.
- Unit tests (Vitest or equivalent).
- Testing Library / playwright a11y assertions.
- axe-core integration.
- Playwright keyboard tests.
- Playwright focus snapshots (visual regression).
- Token contrast validation (
ashlar theme validateagainst defaults). - Forced-colors render checks.
- Reduced-motion render checks where animation is used.
- Section 508 ICT Baseline mapping where the baseline includes an applicable test.
Required for stable components:
- Screen readers: NVDA + Firefox, VoiceOver + Safari, JAWS + Chrome (where available).
- Keyboard only: full operation without mouse.
- Windows High Contrast / forced colors: visible affordances retained.
- Zoom: 200% and 400% browser zoom, 320 CSS px viewport.
- Mobile screen reader spot checks: TalkBack on Android, VoiceOver on iOS.
Manual test scripts are templated (one-page checklists per component) so they are repeatable without prohibitive labor cost.
- Keyboard activatable with Enter and Space.
- Visible focus indicator (
:focus-visible). - Icon-only buttons require accessible name.
- Loading state preserves accessible name; uses
aria-busy="true".
- Focus moves to dialog or first meaningful focusable on open.
- Background content
inertwhile modal is open. - Escape closes (unless flow requires explicit confirmation).
- Dialog has accessible title (visible or
aria-labelledby). - Focus returns to trigger on close.
- Native input fallback (
<input type="date">) where appropriate. - Keyboard navigation across calendar grid.
- Format communicated (label, helper text, or
aria-describedby). - Localization-aware.
- Invalid dates handled clearly.
- Correct combobox role and ARIA states.
- Keyboard navigation across options.
- Filtering preserves screen-reader context.
- Empty state announced.
aria-liveregion for polite/assertive announcement.- Action elements within toast are tab-accessible.
- Auto-dismiss timing respects
prefers-reduced-motionand accessibility timing requirements (WCAG 2.2.1).
- AI-generated content clearly identified.
- Citation/source area always present when configured.
- Review-before-submit interaction for consequential actions.
- Transcript semantics preserved for screen readers.
- Streaming-response accessibility (live region updates).
- No dark patterns.
Files within a capsule that affect accessibility behavior are tagged critical_for_a11y: true in the capsule manifest. The drift management protocol forces user confirmation on any update that touches these files (see [drift-and-updates.md](./drift-and-updates.md)).
A component cannot be marked stable without:
- Complete
*.evidence.jsonwith all required WCAG criteria addressed. - Section 508 ICT Baseline mapping for applicable component behavior.
- Automated accessibility results recorded for the capsule fixture.
- At least one passing manual keyboard test with an actionable evidence reference.
- At least one passing manual screen-reader test with a real screen-reader technology and an actionable transcript/evidence reference.
- Known limitations recorded, even when the list is empty.
- Review metadata recorded and local evidence references resolving to files or HTTPS URLs.
LTS evidence raises the manual matrix to NVDA, VoiceOver, and one of JAWS or TalkBack, plus broader browser and forced-colors checks.
8. CEM _ashlar.antiPatterns populated for known misuse vectors.
9. ast-grep rules generated and tested.
10. Documentation includes "do not" examples and known limitations.
ashlar audit registry checks all of the above before a component graduates to stable.
Every component declares its current accessibility status in the evidence packet:
not-reviewed— no evidence, not for production.automated-tested— passes automated checks, no manual evidence.manual-tested— has manual test records but evidence packet incomplete.stable-evidence— full evidence packet, suitable for stable graduation.known-issue— has open WCAG-impacting issue; avoid for affected use cases.
The status is visible in the registry, in component docs, and queryable via ashlar evidence <component>.
- ADR 0005 — Accessibility target
- Capsule format
- Drift and updates
- WCAG 2.2: https://www.w3.org/TR/WCAG22/
- ARIA 1.3: https://www.w3.org/TR/wai-aria-1.3/
- AccName 1.2: https://www.w3.org/TR/accname-1.2/
- Section 508: https://www.section508.gov/manage/laws-and-policies/
- ADA Title II rule: https://www.ada.gov/resources/2024-03-08-web-rule/
- EAA WCAG 2.2 alignment: https://www.wcag.com/compliance/european-accessibility-act/