CMDash — Voluntary Product Accessibility Template (VPAT®) 2.5 — WCAG Edition
Product: CMDash
Report date: 2026-10-09
Conformance target: WCAG 2.2 Level AA
Evaluation methods used: Automated testing (axe-core 4.13.0 via @axe-core/playwright) over 133 public routes, including every route of a curated top-40 list (scripts/a11y/top-40-routes.mjs); manual evaluation of criteria axe-core cannot judge is tracked separately in docs/compliance/vpat-manual.yaml and defaults to "Not Evaluated" until a human completes it.
Contact: support@cmdash.com
Notes on this report
This VPAT is generated by scripts/a11y/vpat.mjs from an automated axe-core
crawl (ROADMAP-V80 G3) and is not a substitute for a full manual/expert
accessibility audit. Axe-core's own documentation states its automated
ruleset finds roughly 30-50% of WCAG issues; every row below marked
"Automated" reflects only what a machine can check, and every row sourced
from vpat-manual.yaml is honestly "Not Evaluated" until a qualified human
completes that review — this report never marks an unevaluated criterion as
"Supports". ROADMAP-V80 J8 drove the automated findings to zero (design
tokens re-derived for WCAG 1.4.3 in both themes, in-text links given a
non-colour cue for 1.4.1, a <main> landmark added to the six
unauthenticated entry points) and added the guards that keep them there:
node scripts/a11y/check-axe-results.mjs fails the build on any
serious/critical finding in this report's source data, and two token-level
contrast checks (scripts/a11y/check-contrast.mjs,
apps/portal/test/a11y/token-contrast.test.ts) fail it the moment a colour
token drops below its threshold. A row below can still honestly read "Does
Not Support" — nothing here suppresses a finding.
Status summary: Supports: 15 · Not Evaluated: 34 · Not Applicable: 7 (55 testable WCAG 2.2 A+AA criteria, plus 4.1.1 retained as Not Applicable per the WCAG 2.2 errata — 56 rows total)
Table 1: Success Criteria, Level A
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 1.1.1 Non-text Content | Supports | Automated (axe-core 4.13.0, rule(s): aria-meter-name, aria-progressbar-name, image-alt, input-image-alt, object-alt, role-img-alt, svg-img-alt). Zero violations across every audited route where the rule applied. |
| 1.2.1 Audio-only and Video-only (Prerecorded) | Not Evaluated | No prerecorded audio-only or video-only content ships on the audited routes today; confirm on each release and re-evaluate if any is added. |
| 1.2.2 Captions (Prerecorded) | Supports | Automated (axe-core 4.13.0, rule(s): video-caption). Zero violations across every audited route where the rule applied. |
| 1.2.3 Audio Description or Media Alternative (Prerecorded) | Not Evaluated | Applies to the product-walkthrough video on /demo; needs a manual check for an audio-described track or a text alternative. |
| 1.3.1 Info and Relationships | Supports | Automated (axe-core 4.13.0, rule(s): aria-hidden-body, aria-required-children, aria-required-parent, definition-list, dlitem, list, listitem, td-headers-attr, th-has-data-cells). Zero violations across every audited route where the rule applied. |
| 1.3.2 Meaningful Sequence | Not Evaluated | Requires reading each route's DOM order against its visual order with CSS disabled; axe has no rule that judges sequence meaning. |
| 1.3.3 Sensory Characteristics | Not Evaluated | Requires a manual read of instructional copy for shape/position/sound-only references (e.g. 'click the button on the right'). |
| 1.4.1 Use of Color | Supports | Automated (axe-core 4.13.0, rule(s): link-in-text-block). Zero violations across every audited route where the rule applied. |
| 1.4.2 Audio Control | Not Applicable | Automated (axe-core 4.13.0, rule(s): no-autoplay-audio) — the rule never matched applicable content on any audited route. |
| 2.1.1 Keyboard | Supports | Automated (axe-core 4.13.0, rule(s): frame-focusable-content, scrollable-region-focusable, server-side-image-map). Zero violations across every audited route where the rule applied. |
| 2.1.2 No Keyboard Trap | Not Evaluated | ROADMAP-V80 J8 added automated traversal: e2e/portal/keyboard.spec.ts drives the marketing nav's dropdown (Escape closes, focus returns to the trigger) and e2e/keyboard-dashboard.spec.ts drives the command palette and one ui/Dialog (20 Tab + 5 Shift+Tab presses must all stay inside the panel, Escape closes, focus returns to the invoking button), both in light and dark. That is evidence for the surfaces it covers, NOT for every dialog in the product — the dashboard has dozens built on the same primitive and a handful that are not. Status stays Not Evaluated until a human has walked the rest. |
| 2.1.4 Character Key Shortcuts | Not Evaluated | Requires a manual inventory of any single-character keyboard shortcuts (e.g. a command-palette trigger) and confirming each can be turned off, remapped, or is active only on focus. |
| 2.2.1 Timing Adjustable | Not Applicable | Automated (axe-core 4.13.0, rule(s): meta-refresh) — the rule never matched applicable content on any audited route. |
| 2.2.2 Pause, Stop, Hide | Not Applicable | Automated (axe-core 4.13.0, rule(s): blink, marquee) — the rule never matched applicable content on any audited route. |
| 2.3.1 Three Flashes or Below Threshold | Not Evaluated | Requires a manual review of any animated UI (loading states, chart transitions) for flash rate; axe has no rule that measures flash frequency. |
| 2.4.1 Bypass Blocks | Supports | Automated (axe-core 4.13.0, rule(s): bypass). Zero violations across every audited route where the rule applied. |
| 2.4.2 Page Titled | Supports | Automated (axe-core 4.13.0, rule(s): document-title). Zero violations across every audited route where the rule applied. |
| 2.4.3 Focus Order | Not Evaluated | ROADMAP-V80 J8's keyboard specs assert that focus never falls to <body> partway through either app's header/sidebar, that the skip link is the FIRST tab stop and moves focus into the main landmark, and that closing the marketing nav's menu returns focus to its trigger. One KNOWN FAILURE is recorded executably rather than in prose: closing components/ui/Dialog.tsx with Escape leaves focus on <body> instead of the invoking button (e2e/keyboard-dashboard.spec.ts, 'a dialog returns focus to the button that opened it', marked test.fail()). Confirming order matches the VISUAL reading order on each audited route is still a human job. |
| 2.4.4 Link Purpose (In Context) | Supports | Automated (axe-core 4.13.0, rule(s): area-alt, link-name). Zero violations across every audited route where the rule applied. |
| 2.5.1 Pointer Gestures | Not Evaluated | Requires a manual inventory of any multipoint/path-based gesture (none identified yet) and a single-pointer alternative for each. |
| 2.5.2 Pointer Cancellation | Not Evaluated | Requires a manual check that click/tap actions fire on up-event, not down-event, and can be aborted by dragging away before release. |
| 2.5.3 Label in Name | Not Evaluated | axe's label-content-name-mismatch rule covers this but ships disabled by default in axe-core 4.13; requires enabling it manually or a hand review of icon-button accessible names against their visible labels. |
| 2.5.4 Motion Actuation | Not Applicable | No feature responds to device motion or user motion (shake-to-undo and similar); confirm on each release. |
| 3.1.1 Language of Page | Supports | Automated (axe-core 4.13.0, rule(s): html-has-lang, html-lang-valid, html-xml-lang-mismatch). Zero violations across every audited route where the rule applied. |
| 3.2.1 On Focus | Not Evaluated | Requires a manual Tab traversal confirming no element triggers a context change purely on receiving focus. |
| 3.2.2 On Input | Not Evaluated | Requires a manual review of every form confirming no field triggers a context change purely on value change, without a submit action. |
| 3.2.6 Consistent Help | Not Evaluated | New in WCAG 2.2; no axe-core 4.13 rule exists yet. Requires confirming any help/contact mechanism appears in the same relative order on every route that offers one. |
| 3.3.1 Error Identification | Not Evaluated | Requires a manual review of every form's error state confirming errors are identified in text, not by color alone. |
| 3.3.2 Labels or Instructions | Supports | Automated (axe-core 4.13.0, rule(s): form-field-multiple-labels). Zero violations across every audited route where the rule applied. |
| 3.3.7 Redundant Entry | Not Evaluated | New in WCAG 2.2; no axe-core 4.13 rule exists yet. Requires confirming multi-step flows (onboarding, registration) never re-ask for information already supplied in the same process. |
| 4.1.1 Parsing | Not Applicable | Removed from WCAG 2.2 (formerly "Parsing"); retained here for continuity with prior VPATs only. Not required for WCAG 2.2 AA conformance. |
| 4.1.2 Name, Role, Value | Supports | Automated (axe-core 4.13.0, rule(s): area-alt, aria-allowed-attr, aria-braille-equivalent, aria-command-name, aria-conditional-attr, aria-deprecated-role, aria-hidden-body, aria-hidden-focus, aria-input-field-name, aria-prohibited-attr, aria-required-attr, aria-roles, aria-tab-name, aria-toggle-field-name, aria-tooltip-name, aria-valid-attr-value, aria-valid-attr, button-name, duplicate-id-aria, frame-title-unique, frame-title, input-button-name, input-image-alt, label, link-name, nested-interactive, select-name, summary-name). Zero violations across every audited route where the rule applied. |
Table 2: Success Criteria, Level AA
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 1.2.4 Captions (Live) | Not Applicable | No live audio/video content is published on any audited route. |
| 1.2.5 Audio Description (Prerecorded) | Not Evaluated | Same /demo walkthrough video as 1.2.3; needs a manual check for an audio-description track. |
| 1.3.4 Orientation | Not Evaluated | axe's css-orientation-lock rule exists but ships disabled by default in axe-core 4.13; requires a manual portrait/landscape check on a real device. |
| 1.3.5 Identify Input Purpose | Supports | Automated (axe-core 4.13.0, rule(s): autocomplete-valid). Zero violations across every audited route where the rule applied. |
| 1.4.3 Contrast (Minimum) | Supports | Automated (axe-core 4.13.0, rule(s): color-contrast). Zero violations across every audited route where the rule applied. |
| 1.4.4 Resize Text | Supports | Automated (axe-core 4.13.0, rule(s): meta-viewport). Zero violations across every audited route where the rule applied. |
| 1.4.5 Images of Text | Not Evaluated | Requires a manual sweep for text baked into raster images (logos and brand marks are exempt) across the audited routes. |
| 1.4.10 Reflow | Not Evaluated | Requires manually resizing the viewport to 320px CSS width per route and confirming no horizontal scroll or lost content. |
| 1.4.11 Non-text Contrast | Not Evaluated | Covered in part by scripts/a11y/check-contrast.mjs and apps/portal/test/a11y/token-contrast.test.ts, which since ROADMAP-V80 J8 also assert --v-border-active (the focus ring and the active input border) at WCAG's 3:1 non-text threshold against each surface. Both still check colour VALUES, not rendered boundaries; a manual pass over icons, form-field borders and focus indicators is still owed. |
| 1.4.12 Text Spacing | Supports | Automated (axe-core 4.13.0, rule(s): avoid-inline-spacing). Zero violations across every audited route where the rule applied. |
| 1.4.13 Content on Hover or Focus | Not Evaluated | Requires manually triggering every tooltip/popover (Tooltip, InfoTip — V71 A9/F3) and confirming it is dismissible, hoverable and persistent per the criterion. |
| 2.4.5 Multiple Ways | Not Evaluated | Requires confirming each page is reachable via more than one path (nav + sitemap + search) where more than one process step exists. |
| 2.4.6 Headings and Labels | Not Evaluated | Requires a manual read of heading/label text for descriptiveness; axe can confirm structure (see 1.3.1) but not whether wording is meaningful. |
| 2.4.7 Focus Visible | Not Evaluated | ROADMAP-V80 J8 widened this: both keyboard specs now assert a non-'none' outline or a box-shadow on every focus stop they walk (8 on the portal's /pricing, 20 across the dashboard shell), in BOTH themes — which matters because the ring is --v-green and that token differs per theme. Still short of the criterion, which is every interactive element on every audited route; status stays Not Evaluated. |
| 2.4.11 Focus Not Obscured (Minimum) | Not Evaluated | New in WCAG 2.2; no axe-core 4.13 rule exists yet. Requires a manual check that a sticky header/footer never fully hides a focused element. |
| 2.5.7 Dragging Movements | Not Evaluated | New in WCAG 2.2; no axe-core 4.13 rule exists yet. Requires a manual inventory of drag interactions (if any) and a single-pointer alternative for each. |
| 2.5.8 Target Size (Minimum) | Not Evaluated | axe's target-size rule covers this but ships disabled by default (experimental) in axe-core 4.13; requires enabling it manually or a hand measurement of small interactive targets (icon buttons, table row actions). |
| 3.1.2 Language of Parts | Not Applicable | Automated (axe-core 4.13.0, rule(s): valid-lang) — the rule never matched applicable content on any audited route. |
| 3.2.3 Consistent Navigation | Not Evaluated | Requires comparing MarketingNav/MarketingFooter and the dashboard nav's relative order across every route they appear on. |
| 3.2.4 Consistent Identification | Not Evaluated | Requires confirming repeated components (e.g. the same icon button) carry the same accessible name everywhere they appear. |
| 3.3.3 Error Suggestion | Not Evaluated | Requires a manual review of every form's validation copy for a correction suggestion, not just an error flag. |
| 3.3.4 Error Prevention (Legal, Financial, Data) | Not Evaluated | Applies to checkout/billing changes and account deletion; requires confirming each offers a review, confirm, or reversal step. |
| 3.3.8 Accessible Authentication (Minimum) | Not Evaluated | New in WCAG 2.2; no axe-core 4.13 rule exists yet. Requires confirming /login and /register do not require a cognitive function test (e.g. transcribing a CAPTCHA) without an alternative — password-manager autofill and magic-link sign-in both count as accepted alternatives. |
| 4.1.3 Status Messages | Not Evaluated | Requires a manual screen-reader check that toast/inline status messages (form submit confirmations, save indicators) use role=status/alert or aria-live rather than relying on visual change alone. |
Generated by node scripts/a11y/vpat.mjs. Do not hand-edit — edit
docs/compliance/vpat-manual.yaml for manual criteria, or re-run the
e2e/portal/a11y.spec.ts suite for automated ones, then regenerate.