CitySchedule
Municipal Appointment Management
Accessibility Conformance Report (VPAT®)Based on VPAT® Version 2.5
WCAG and Revised Section 508 editions
CitySchedule LLC
WCAG and Revised Section 508 editions
CitySchedule LLC
Product Information
| Name of Product / Version | CitySchedule — Municipal Reservation & Inspection Record Management (web application), v0.1.0 |
|---|---|
| Product Description | Web-based platform for residents to book municipal appointments and for city staff/administrators to review, schedule, inspect, and report on those reservations. |
| Date | 2026-06-22 |
| Contact Information | support@cityschedule.app |
| Notes | This report covers the responsive web application served at all dashboard, authentication, and public marketing routes. |
| Evaluation Methods Used | Manual source-code review of shared UI primitives and representative pages; keyboard-only operation review; component unit tests (vitest, including dialog focus-trap and name/role/value assertions); inspection of color tokens against WCAG contrast ratios; automated axe-core scans (@axe-core/playwright, WCAG 2.1 A/AA) of every page reachable without authentication, run in the Playwright e2e suite (npm run test:e2e:a11y) across desktop and mobile viewports; automated axe-core checks of authenticated components (the reservations table, report charts/data-tables, inspection-record rows, and the reservation detail dialog) rendered in jsdom via the vitest suite; and a full-page authenticated-route axe scan (@axe-core/playwright) that signs in per role through the real UI and scans every dashboard route (npm run test:e2e:a11y:auth), gated on seeded test-user credentials; and a manual NVDA (Windows) bench test of the booking flow and admin dashboard (docs/accessibility-at-bench-test.md), which surfaced and closed a set of announcement/labeling defects. JAWS and VoiceOver (macOS/iOS) bench passes and the remaining scripted flows have not yet been run; completing them is recommended before raising FPC 302.1 to full Supports. |
Applicable Standards / Guidelines
| Standard / Guideline | Included In Report |
|---|---|
| Web Content Accessibility Guidelines (WCAG) 2.1 | Level A — Yes; Level AA — Yes; Level AAA — No |
| Revised Section 508 (2017) — 36 CFR 1194 Appendix A, B, C | Yes (Chapters 3, 4, 5, 6 as applicable to a web application) |
| EN 301 549 | Not separately evaluated (substantially overlaps the WCAG report) |
Terms
- Supports — The functionality of the product has at least one method that meets the criterion without known defects, or meets with equivalent facilitation.
- Partially Supports — Some functionality of the product does not meet the criterion.
- Does Not Support — The majority of product functionality does not meet the criterion.
- Not Applicable — The criterion is not relevant to the product.
- Not Evaluated — The product has not been evaluated against the criterion. (Used only for WCAG 2.x Level AAA.)
WCAG 2.1 Report
Conformance level reflects the state of the codebase as of the date above, including the accessibility remediation described in Appendix: Remediation Applied.
Table 1 — Success Criteria, Level A
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 1.1.1 Non-text Content | Supports | Functional images (e.g. MFA QR code, inspection photos) have descriptive alt text. Decorative emoji used as iconography across navigation, stat cards, banners, empty states, and search are marked aria-hidden so they are not announced. Data charts expose an equivalent visually-hidden data table; the decorative SVG is hidden from assistive technology. |
| 1.2.1 Audio-only / Video-only (Prerecorded) | Not Applicable | No pre-recorded audio or video content. |
| 1.2.2 Captions (Prerecorded) | Not Applicable | No video content. |
| 1.2.3 Audio Description or Media Alternative | Not Applicable | No video content. |
| 1.3.1 Info and Relationships | Supports | Forms use programmatic <label htmlFor> associations; required state via aria-required; data tables use <thead>/<th scope>; the dialog uses role="dialog" + aria-labelledby. |
| 1.3.2 Meaningful Sequence | Supports | DOM order matches visual reading order; responsive reflow is driven by CSS without reordering meaningful content in a way that breaks sequence. |
| 1.3.3 Sensory Characteristics | Supports | Instructions do not rely solely on shape, size, or position; controls carry text labels or aria-labels. |
| 1.4.1 Use of Color | Supports | Status is conveyed by a text label in addition to a color dot/badge; form errors combine red color with an aria-invalid state and a text error message. |
| 1.4.2 Audio Control | Not Applicable | No auto-playing audio. |
| 2.1.1 Keyboard | Supports | All interactive elements are keyboard-operable. Native controls (buttons, links, inputs, selects) are inherently operable; non-native clickable elements — the shared Card primitive, the reservations-table rows, the desktop schedule appointment blocks, the admin dashboard appointment cards, the inspection-record disclosure headers (exposed as buttons with aria-expanded), and the file-upload dropzones — are focusable and activate on Enter/Space with descriptive accessible names. A source-level sweep of dashboard onClick handlers confirmed no remaining mouse-only interactive elements (the only non-native onClicks left are non-interactive event-propagation guards and overlay/backdrop click-to-close shortcuts that have keyboard equivalents). |
| 2.1.2 No Keyboard Trap | Supports | The modal dialog implements a deliberate, escapable focus trap (Tab cycles within, Escape closes, focus is restored to the opener). No unintended traps identified. |
| 2.1.4 Character Key Shortcuts | Not Applicable | No single-character key shortcuts are implemented. |
| 2.2.1 Timing Adjustable | Supports | No time limits are imposed on user interactions other than authentication/session expiry handled by the identity provider. |
| 2.2.2 Pause, Stop, Hide | Supports | No auto-updating or moving content beyond brief entry animations, which are suppressed under prefers-reduced-motion. |
| 2.3.1 Three Flashes or Below Threshold | Supports | No flashing content. |
| 2.4.1 Bypass Blocks | Supports | A "Skip to main content" link is the first focusable element in the dashboard layout and targets the <main> landmark. |
| 2.4.2 Page Titled | Supports | Each route has a descriptive title: legal/marketing routes via Next.js metadata, and the (client-rendered) dashboard and authentication routes set a per-page document.title from their heading ({Page} · CitySchedule). |
| 2.4.3 Focus Order | Supports | Focus order follows DOM/visual order; the dialog constrains focus to its contents while open and restores it on close; on client-side route changes focus is moved to the main content landmark so the newly rendered page is the next focus stop rather than the activated sidebar link. |
| 2.4.4 Link Purpose (In Context) | Supports | Links and link-styled controls have descriptive text; icon-only controls provide an aria-label. |
| 2.5.1 Pointer Gestures | Supports | No multi-point or path-based gestures are required. Drag-to-reorder navigation provides a documented keyboard alternative (arrow/Home/End keys). |
| 2.5.2 Pointer Cancellation | Supports | Activation occurs on the up-event for native controls; no down-event-only actions. |
| 2.5.3 Label in Name | Supports | Visible control text is included in the accessible name; icon-only controls use matching aria-labels. |
| 2.5.4 Motion Actuation | Not Applicable | No device-motion or user-motion actuation. |
| 3.1.1 Language of Page | Supports | The root <html lang="en"> is set. |
| 3.2.1 On Focus | Supports | Focusing a control does not trigger a change of context. |
| 3.2.2 On Input | Supports | Changing a form value does not automatically change context; submission is explicit. |
| 3.3.1 Error Identification | Supports | Validation errors are conveyed in text, associated via aria-describedby, and surfaced through role="alert" regions. |
| 3.3.2 Labels or Instructions | Supports | Inputs, selects, and textareas provide visible labels (or aria-label for compact filters) and required-field indicators. |
| 4.1.1 Parsing | Supports | Markup is generated by React/Next.js producing well-formed, uniquely-IDed (via useId) DOM. (This criterion is also obsolete in WCAG 2.2.) |
| 4.1.2 Name, Role, Value | Supports | Native elements provide implicit roles; custom controls (dialog, account menu, reorder handles, clickable cards/rows) expose appropriate role, state (aria-expanded, aria-pressed, aria-modal), and accessible names. Unit tests assert the dialog's name/role/value. |
Table 2 — Success Criteria, Level AA
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 1.2.4 Captions (Live) | Not Applicable | No live media. |
| 1.2.5 Audio Description (Prerecorded) | Not Applicable | No video content. |
| 1.3.4 Orientation | Supports | Layout is responsive and not locked to a single orientation; portrait/landscape both render. |
| 1.3.5 Identify Input Purpose | Supports | Personal-data inputs carry programmatic autocomplete tokens: authentication fields (name, email, current-password, new-password, one-time-code) and the shared Input primitive forwards autoComplete/inputMode, set on the account name (name), booking phone (tel), and service address (street-address) fields. |
| 1.4.3 Contrast (Minimum) | Supports | Color tokens were tuned for AA: secondary/muted text uses #687284 (≥4.5:1 on white); primary text is dark navy on light surfaces. Filled gold buttons and gold link text now use a deeper gold (#946a22, ≥4.5:1 with white) instead of the brand #b8863a (which sat at 3.22:1); the brand gold is retained for non-text accents/logos. The landing footer text, marketing-page tagline, and auth "or" divider were darkened/lightened to clear 4.5:1. The automated axe-core scan reports zero color-contrast violations across all public pages on desktop and mobile. |
| 1.4.4 Resize Text | Supports | Layout uses relative units and clamp() for headings; content reflows when text is zoomed to 200%. |
| 1.4.5 Images of Text | Supports | Text is rendered as live text, not images. |
| 1.4.10 Reflow | Supports | The app reflows to a single-column layout at narrow widths (down to ~320 CSS px) without loss of content or horizontal scrolling; the sidebar collapses behind a menu. |
| 1.4.11 Non-text Contrast | Supports | Control borders, the focus indicator (2px solid navy-light with offset), and status dots provide ≥3:1 contrast against adjacent colors. |
| 1.4.12 Text Spacing | Supports | Line-height of 1.6 and label spacing are set with relative units; no fixed-height text containers clip content under user spacing overrides. |
| 1.4.13 Content on Hover or Focus | Supports | Chart tooltips appear on hover; the equivalent data is provided in a visually-hidden table and the chart SVG is hidden from assistive technology. Information formerly available only through a native title tooltip is now presented as persistent text (denied-reservation reasons) or carried in the control's accessible name (file-upload errors, unavailable time-slot reasons). Remaining title attributes are redundant labels on icon controls that also expose an accessible name, or duplicate text already present in the DOM; none is the sole source of information. |
| 2.4.5 Multiple Ways | Supports | Content is reachable via the persistent role-based navigation and, in list views, via search/filter. |
| 2.4.6 Headings and Labels | Supports | Pages use a descriptive <h1> (PageHeader) and section <h2>s; form fields are clearly labeled. |
| 2.4.7 Focus Visible | Supports | A global :focus-visible outline (2px, offset) is applied; the only suppressions are non-interactive scroll anchors and non-interactive chart SVG layers. |
| 3.1.2 Language of Parts | Not Applicable | Content is single-language (English). |
| 3.2.3 Consistent Navigation | Supports | The role-based sidebar presents navigation in a consistent relative order across pages (user reordering is opt-in and persists per role). |
| 3.2.4 Consistent Identification | Supports | Shared components (badges, buttons, status labels) are used consistently across the app. |
| 3.3.3 Error Suggestion | Supports | Validation messages describe the problem and expected input where the cause is known. |
| 3.3.4 Error Prevention (Legal, Financial, Data) | Supports | Consequential actions (deletions, decisions) are performed through explicit confirm dialogs and are reversible/auditable where applicable. |
| 4.1.3 Status Messages | Supports | Status changes are announced via role="alert" (form errors) and an aria-live="polite" region (navigation reorder announcements) without moving focus. |
WCAG 2.1 Level AAA criteria were Not Evaluated and are out of scope for this report.
Revised Section 508 Report
Chapter 3: Functional Performance Criteria (FPC)
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 302.1 Without Vision | Partially Supports | Semantic structure, labels, text alternatives, live-region announcements, and a data-table alternative to charts support non-visual use; all interactive elements are now keyboard-operable (2.1.1 supported). A manual NVDA (Windows) bench test of the booking flow and admin dashboard was executed (docs/accessibility-at-bench-test.md); it surfaced a set of announcement/labeling defects (unlabeled booking groups and selects, silent custom modals, and clickable cards with empty accessible names) that have been fixed and re-verified to announce correctly. This remains Partially Supports pending JAWS/VoiceOver coverage and an end-to-end run of the remaining flows before it is raised to full Supports. |
| 302.2 With Limited Vision | Supports | Meets contrast minimums, supports zoom/reflow to 200%+, and provides a visible focus indicator. |
| 302.3 Without Perception of Color | Supports | Information is not conveyed by color alone (text labels accompany status color). |
| 302.4 Without Hearing | Not Applicable | No audio output. |
| 302.5 With Limited Hearing | Not Applicable | No audio output. |
| 302.6 Without Speech | Not Applicable | No speech input required. |
| 302.7 With Limited Manipulation | Supports | Primary flows are operable by keyboard with native controls, keyboard-operable custom controls, and generous mobile touch targets; the previously-noted bespoke clickable elements have been remediated (see 2.1.1). |
| 302.8 With Limited Reach and Strength | Supports | No physical reach/strength requirement; standard pointer/keyboard input. |
| 302.9 With Limited Language, Cognitive, and Learning Abilities | Supports | Consistent navigation, clear labels, error suggestions, and confirmation of consequential actions. |
Chapter 4: Hardware
Not Applicable (software-only web application).
Chapter 5: Software
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 501.1 Scope / 502 / 503 | See WCAG | As a web application, software requirements are satisfied through the WCAG 2.1 A/AA report above (per 508 §504.2 / E207.2). |
| 504 Authoring Tool | Not Applicable | The product is not an authoring tool for third-party content. |
Chapter 6: Support Documentation and Services
| Criteria | Conformance Level | Remarks and Explanations |
|---|---|---|
| 602.2 Accessibility and Compatibility Features | Supports | A public Accessibility Statement (/accessibility) documents the conformance target, accessibility features, known limitations, and supported browser/AT combinations; this VPAT is available to customers on request. |
| 602.3 Electronic Support Documentation | Supports | In-app legal/support pages and the Accessibility Statement are standard accessible HTML, linked from the site footer. |
| 603 Support Services | Supports | Support is offered through standard accessible channels. |
Appendix: Remediation Applied (2026-06-22)
The following fixes were made during this accessibility pass and are reflected in the conformance levels above:
- Decorative iconography hidden from assistive tech — emoji icons in the sidebar navigation/account menu, stat cards, empty states, warning banners, the search field, the city-select placeholder, and report engagement rows are now
aria-hidden, eliminating screen-reader noise (1.1.1). - Charts given a text alternative — each bar/pie chart in Reports now renders a visually-hidden, captioned
<table>of the underlying figures, and the decorative SVG isaria-hiddenso the data is announced exactly once (1.1.1). - Clickable non-native elements made keyboard-operable — the shared
Card, inspection-record rows, reservations-table rows, and the desktop schedule appointment blocks now exposerole="button", are focusable (tabIndex=0), and activate on Enter/Space, with descriptivearia-labels (2.1.1 / 4.1.2). - Input purpose identified — an
autoComplete/inputModeprop was added to the sharedInputprimitive and wired to the account name, booking phone, and service-address fields; auth fields already carried the correct tokens (1.3.5). - Color-contrast failures fixed — a deeper gold (
#946a22) replaces the brand gold on filled buttons and link text (white-on-gold was 3.22:1); the landing footer text, marketing-page tagline, and auth "or" divider were corrected. The hover state that re-brightened CTAs (dropping them below threshold) was removed (1.4.3). - Automated accessibility gate added —
@axe-core/playwrightscans every unauthenticated page (WCAG 2.1 A/AA) on desktop and mobile in the e2e suite (npm run test:e2e:a11y); it currently reports zero violations and guards against regressions. - Authenticated components brought under automated scan — axe-core checks of the reservations table, report charts/data tables, inspection-record rows, and the reservation detail dialog were added to the
vitestsuite (src/test/a11y/). This surfaced and fixed an unlabeled sort<select>and date filter in the reservations table (4.1.2 / 1.3.1). - Per-route page titles — dashboard and authentication routes now set a descriptive
document.titlefrom their heading; legal/marketing routes use Next.js metadata (2.4.2). - Public Accessibility Statement published — /accessibility documents the conformance target, features, known limitations, supported AT, and a barrier-reporting channel with a 5-business-day response commitment; linked from the site footer (508 §602; ADA Title II program expectation).
- Notification emails — the email shell now declares
lang="en", uses a semantic<h1>, and its gray label/footer/subtitle text was darkened to meet 4.5:1; the Turnstile captcha region carries an accessible group label. - Keyboard sweep completed — the remaining mouse-only clickable elements were made keyboard-operable: the admin dashboard appointment cards, the resident and admin inspection-record disclosure headers (now real/role
buttons witharia-expanded, with the rename/remove actions kept as separate non-nested controls), and the resident booking and reservation-detail file-upload dropzones. This moves 2.1.1 and FPC 302.7 to full Supports (2.1.1 / 4.1.2). - Pointer-only tooltips remediated — denied-reservation reasons now render as persistent wrapping text instead of a clipped
title; the inspection-photo upload Retry error and unavailable time-slot reasons are exposed in their control's accessible name. No essential information is conveyed solely through a nativetitletooltip, moving 1.4.13 to full Supports (1.4.13). - Authenticated full-route scan added and run — a Playwright + axe-core suite signs in per role through the real UI (clearing mandatory MFA via a seeded TOTP secret) and scans every dashboard route at the page level (
npm run test:e2e:a11y:auth), extending coverage from key components to whole authenticated pages. Running it surfaced and fixed real defects: insufficient contrast of muted secondary text on the cream page background (1.4.3), unlabeled date/time inputs on the booking and calendar screens (1.3.1 / 4.1.2), and a focusable chart surface inside anaria-hiddenwrapper on reports (4.1.2). All dashboard routes now scan clean across resident, admin, and super-admin roles. It is gated on seeded test-user credentials so it skips cleanly where they are not configured. - Manual screen-reader findings addressed — a keyboard/screen-reader pass of the booking flow and admin dashboard prompted a batch of fixes: the shared
Selectno longer suppresses its focus ring; the two bespoke portal modals (reservation detail, inspection result) gained full dialog semantics and a focus trap via a shareduseModalA11yhook; booking groups/labels (assign inspection records, sub-service type, attach-documents dropzone, download-form actions) were programmatically associated; and icon/ambiguous controls across the admin managers (disclosure toggles, view/remove/edit/reset/delete buttons) and the schedule and reservation list cards received descriptive accessible names (1.3.1 / 2.4.3 / 2.4.7 / 4.1.2). - Route-change focus management & residual label sweep — on client-side navigation, keyboard focus is now moved to the main content landmark (which carries
tabIndex=-1) so screen-reader and keyboard users land at the start of the newly rendered page instead of remaining on the activated sidebar link, making the page change perceivable (2.4.3). A follow-up sweep added accessible names to the last icon-only controls whose name had been a bare glyph — the department/template/record rename (✎) buttons, the replace/remove (↻/×) actions on submitted-form chips, and the remove-attachment (×) buttons in the reservation-detail and resident reservation views — and an explicitaria-labelto the department filter<select>s that had relied on atitletooltip alone (2.4.4 / 2.5.3 / 4.1.2).
Appendix: Recommended Next Steps
- Execute the scripted NVDA/JAWS/VoiceOver bench test (
docs/accessibility-at-bench-test.md) to validate the non-visual "Supports" claims and close out FPC 302.1; feed any defects back before flipping it to full Supports. - Provision per-role seeded test users in the CI environment so the authenticated full-route axe scan (
npm run test:e2e:a11y:auth) runs on every build rather than only where credentials are configured. - Confirm with each city that uploaded/city-supplied blank forms have an accessible alternative format available on request (the platform's barrier-reporting channel already offers this for residents).
VPAT® is a registered trademark of the Information Technology Industry Council (ITI). This document follows the VPAT 2.5 structure but is an internal conformance self-assessment, not a certification.