Accessibility Conformance
What this is, and what it is not
A New Jersey district evaluating DataVot will have an accessibility question on its checklist. This page answers it with evidence rather than a claim.
It is not a VPAT. A VPAT is a specific document produced against an ITI template, and the ones districts trust are completed by an independent accessibility firm. DataVot has not commissioned one. Saying so plainly is deliberate — our Questions and answers page says the same thing — because a self-declared conformance statement dressed up as an audit is worse than an honest self-assessment, and a district that discovers the difference later has every reason to distrust everything else we have told it.
What it is: a criterion-by-criterion record of where DataVot stands, with the evidence named for each one, and with the difference between machine-verified, verified by inspection and not applicable stated rather than blurred.
Methodology, and its limits
Three automated suites run on every deploy. None of them is a person using the product with a screen reader, and that is the most important sentence on this page.
| Suite | What it does | Scope |
|---|---|---|
| Accessibility | axe-core at WCAG 2.1 A/AA against the running application in headless Chrome | 18 surfaces, signed-out and signed-in |
| Contrast | Computes contrast ratios and fails below the AA floor | 54 colour pairs — hardcoded pairs, design-token pairs in both light and dark themes, and text against every surface defined in its own scope |
| Static rules | Build-time rules including alt-text presence, external-link relationships and token contrast | Whole source tree |
Automated testing detects roughly a third of WCAG issues, and axe's own documentation says so. It cannot judge whether alt text is meaningful, whether a heading structure matches the visual hierarchy, whether focus order is logical, or whether an error message is understandable. Every criterion below marked Inspected was assessed by a person reading the markup and operating the interface; those judgements are ours and have not been independently reviewed.
The 18 surfaces under automated test, named rather than summarised so the list is evidence you can check — including this page. Signed out: /, /about, /faq, /pricing, /contact, /privacy, /accessibility, /security, /articles, /forum. Signed in: /, /reports, /dashboard, /board-report, /help, /admin/users, /system-admin, /database.
Level A
| Criterion | Conformance | Evidence |
|---|---|---|
| 1.1.1 Non-text Content | Supports | axe image-alt across 16 surfaces; invariant img-alt fails the build on a missing alt. Decorative thumbnails in article prev/next controls carry alt="" deliberately — the enclosing button already exposes the article title, and describing them would announce every title twice |
| 1.2.1 Audio-only / Video-only | Not applicable | No audio or video anywhere in the product. The /help demonstrations are CSS animations, not media, and each carries a text label |
| 1.2.2 Captions (Prerecorded) | Not applicable | No prerecorded media |
| 1.2.3 Audio Description or Media Alternative | Not applicable | No prerecorded media |
| 1.3.1 Info and Relationships | Supports | axe label, form-field-multiple-labels, list, definition-list, table-* rules. Data tables use real <th scope>; the pivot output is a <table>, not a grid of <div>s |
| 1.3.2 Meaningful Sequence | Inspected | DOM order matches visual order; no CSS reordering that changes reading sequence |
| 1.3.3 Sensory Characteristics | Inspected | No instruction depends on shape, position or colour alone |
| 1.3.4 Orientation (AA in 2.1, listed here for completeness) | Supports | No orientation lock; layout is responsive |
| 1.3.5 Identify Input Purpose | Partially supports | Email and password fields carry appropriate autocomplete. Not every field in the admin screens declares a purpose token; those fields have no standard token in the WCAG list |
| 1.4.1 Use of Colour | Inspected | Performance levels are labelled as well as coloured; the dashboard legend names each level in text. Form errors carry text, never colour alone |
| 1.4.2 Audio Control | Not applicable | Nothing plays automatically |
| 2.1.1 Keyboard | Inspected | Every control is a real <button>, <a> or form element. Modals route through useDialog, the single accessibility primitive — Escape to close, focus trap, focus restore |
| 2.1.2 No Keyboard Trap | Supports | The only focus trap is the modal one, which Escape releases and which restores focus to the opener. useDialog is the sole implementation, and no page hand-rolls a second overlay |
| 2.1.4 Character Key Shortcuts | Not applicable | No single-character shortcuts |
| 2.2.1 Timing Adjustable | Partially supports | Sessions expire on a 30-minute idle window (14 days with remember-me) per NIST SP 800-63B AAL2. The window is not user-adjustable; it is a security control a district asks for by name, and work is not lost silently — an expired save returns an error rather than discarding |
| 2.2.2 Pause, Stop, Hide | Supports | The only motion is the /help demos, which respect prefers-reduced-motion by showing the finished state instead of animating |
| 2.3.1 Three Flashes | Supports | Nothing flashes |
| 2.4.1 Bypass Blocks | Partially supports | Landmark structure (<header>, <main>, <nav>, <footer>) lets assistive technology skip the header. A visible skip link is not implemented — see Known gaps |
| 2.4.2 Page Titled | Supports | Every public route is a server component exporting metadata with its own title; npm run test:seo fails the build on a route that is not classified and titled |
| 2.4.3 Focus Order | Inspected | Focus order follows DOM order; useDialog restores focus to the invoking control on close |
| 2.4.4 Link Purpose (In Context) | Inspected | No bare "click here". External links open in a new tab and are marked; external-link-rel pins rel="noopener noreferrer" |
| 2.5.1 Pointer Gestures | Not applicable | No multipoint or path-based gestures |
| 2.5.2 Pointer Cancellation | Supports | All activation is on click, not mousedown |
| 2.5.3 Label in Name | Supports | axe label-content-name-mismatch; visible labels are the accessible names |
| 2.5.4 Motion Actuation | Not applicable | Nothing is operated by device motion |
| 3.1.1 Language of Page | Supports | <html lang> set; axe html-has-lang and html-lang-valid |
| 3.2.1 On Focus | Supports | No context change on focus |
| 3.2.2 On Input | Supports | No form auto-submits on input. Dashboard selectors update the URL and the chart — the same page, not a context change |
| 3.3.1 Error Identification | Supports | Errors are text in a role="alert" region, identified in prose |
| 3.3.2 Labels or Instructions | Supports | axe label; every required field carries a visible red asterisk plus required/aria-required, enforced as a house rule in CLAUDE.md |
| 4.1.1 Parsing | Supports | Obsolete in WCAG 2.2; assessed here as axe duplicate-id and valid nesting |
| 4.1.2 Name, Role, Value | Supports | axe aria-* rule set across 16 surfaces. Current page is marked aria-current="page"; dialogs carry role="dialog" and aria-modal from useDialog |
Level AA
| Criterion | Conformance | Evidence |
|---|---|---|
| 1.2.4 Captions (Live) | Not applicable | No live media |
| 1.2.5 Audio Description | Not applicable | No prerecorded media |
| 1.3.4 Orientation | Supports | No orientation restriction |
| 1.3.5 Identify Input Purpose | Partially supports | See Level A row above |
| 1.4.3 Contrast (Minimum) | Supports | npm run test:contrast — 54 pairs, both themes, failing below AA. It measures a *rendered* property rather than a code property, which is why it caught defects no other check could: three token pairs failing on their own dim wash, and --text-muted at 4.31:1 on the printable document sheet. The large-text exemption is applied from each rule's own font size and weight; an unknown size is treated as normal text |
| 1.4.4 Resize Text | Supports | Layout is responsive with relative units; content reflows to 200% without loss |
| 1.4.5 Images of Text | Supports | No images of text. The marketing screenshots on /overview are pictures of the product, which the exception permits, and each carries alt text |
| 1.4.10 Reflow | Supports | Responsive at 320 CSS px; wide content (tables, the pivot) scrolls inside its own container rather than the page scrolling sideways |
| 1.4.11 Non-text Contrast | Partially supports | Focus indicators and control borders were reviewed against the 3:1 floor by inspection. test:contrast measures text pairs; it does not yet measure UI-component boundaries — see Known gaps |
| 1.4.12 Text Spacing | Inspected | No fixed line-height or letter-spacing that would clip at the WCAG spacing overrides |
| 1.4.13 Content on Hover or Focus | Supports | Popovers are dismissible with Escape and stay open while hovered |
| 2.4.5 Multiple Ways | Supports | Header navigation, footer navigation, /about#documents, in-page search on list screens, and a sitemap |
| 2.4.6 Headings and Labels | Inspected | Exactly one <h1> per page — a house rule; the admin shells render a hidden <h1> only on their gate screens so the rule holds there too |
| 2.4.7 Focus Visible | Supports | :focus-visible outlines are defined and were not removed; a @media (forced-colors: active) block is carried in base.css |
| 3.1.2 Language of Parts | Not applicable | Single-language content (English) |
| 3.2.3 Consistent Navigation | Supports | Exactly one header implementation (AppMenubar) and one footer (SiteFooter), pinned by invariants one-header and one-footer. A second header cannot be introduced without failing the build |
| 3.2.4 Consistent Identification | Supports | Shared components for repeated functions — one clipboard button, one dialog primitive, one table viewer |
| 3.3.3 Error Suggestion | Supports | Errors say what to do next: a blocked sign-in offers resend-verification, a failed contact send names the direct email address rather than reporting a false success |
| 3.3.4 Error Prevention (Legal, Financial, Data) | Supports | Destructive actions confirm. Report saves are guarded against last-write-wins with a 409 rather than silently overwriting, and a data set can never be overwritten by a report save |
| 4.1.3 Status Messages | Partially supports | Errors and confirmations use role="alert". Not every asynchronous status (for example a long upload) announces through a live region — see Known gaps |
Known gaps
Stated here rather than buried, because a district's reviewer will find them and we would rather you learn them from us.
- No independent audit and no VPAT. The whole basis of this document is a vendor self-assessment.
- No screen-reader user testing. Markup has been assessed; the product has not been operated end to end by a person who relies on a screen reader daily. This is the largest gap and no automated suite closes it.
- No visible skip link (2.4.1). Landmarks are present, so assistive technology can skip the header, but a sighted keyboard user tabs through the navigation on every page.
- Non-text contrast (1.4.11) is inspected, not measured. The contrast suite covers text pairs; UI component boundaries and focus indicators are not yet computed against the 3:1 floor.
- Status messages (4.1.3) are partial. Errors announce; some long-running progress states do not.
- Dashboard charts convey information visually. Every chart has a data table equivalent reachable in the product, and the board report exports CSV and TSV — but the chart itself is not individually described.
How to read this in a procurement review
- Every Supports backed by a named suite is re-verified on every deploy, not on the day this page was written. The accessibility and contrast suites run before a release; a regression blocks it.
- Every Inspected row is a human judgement of ours and carries the weight of one.
- Every Partially supports and every entry under Known gaps is a real limitation we would rather you learn from us than from your own reviewer.
Questions, or a district checklist you need completed against this: use the security contact named in the preamble of our Data Privacy Agreement, or the contact page.