CitySchedule

Municipal Appointment Management
Accessibility Conformance Report (VPAT®)Based on VPAT® Version 2.5
WCAG and Revised Section 508 editions
CitySchedule LLC

Product Information

Name of Product / VersionCitySchedule — Municipal Reservation & Inspection Record Management (web application), v0.1.0
Product DescriptionWeb-based platform for residents to book municipal appointments and for city staff/administrators to review, schedule, inspect, and report on those reservations.
Date2026-06-22
Contact Informationsupport@cityschedule.app
NotesThis report covers the responsive web application served at all dashboard, authentication, and public marketing routes.
Evaluation Methods UsedManual 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 / GuidelineIncluded In Report
Web Content Accessibility Guidelines (WCAG) 2.1Level A — Yes; Level AA — Yes; Level AAA — No
Revised Section 508 (2017) — 36 CFR 1194 Appendix A, B, CYes (Chapters 3, 4, 5, 6 as applicable to a web application)
EN 301 549Not 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

CriteriaConformance LevelRemarks and Explanations
1.1.1 Non-text ContentSupportsFunctional 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 ApplicableNo pre-recorded audio or video content.
1.2.2 Captions (Prerecorded)Not ApplicableNo video content.
1.2.3 Audio Description or Media AlternativeNot ApplicableNo video content.
1.3.1 Info and RelationshipsSupportsForms 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 SequenceSupportsDOM 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 CharacteristicsSupportsInstructions do not rely solely on shape, size, or position; controls carry text labels or aria-labels.
1.4.1 Use of ColorSupportsStatus 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 ControlNot ApplicableNo auto-playing audio.
2.1.1 KeyboardSupportsAll 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 TrapSupportsThe 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 ShortcutsNot ApplicableNo single-character key shortcuts are implemented.
2.2.1 Timing AdjustableSupportsNo time limits are imposed on user interactions other than authentication/session expiry handled by the identity provider.
2.2.2 Pause, Stop, HideSupportsNo auto-updating or moving content beyond brief entry animations, which are suppressed under prefers-reduced-motion.
2.3.1 Three Flashes or Below ThresholdSupportsNo flashing content.
2.4.1 Bypass BlocksSupportsA "Skip to main content" link is the first focusable element in the dashboard layout and targets the <main> landmark.
2.4.2 Page TitledSupportsEach 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 OrderSupportsFocus 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)SupportsLinks and link-styled controls have descriptive text; icon-only controls provide an aria-label.
2.5.1 Pointer GesturesSupportsNo 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 CancellationSupportsActivation occurs on the up-event for native controls; no down-event-only actions.
2.5.3 Label in NameSupportsVisible control text is included in the accessible name; icon-only controls use matching aria-labels.
2.5.4 Motion ActuationNot ApplicableNo device-motion or user-motion actuation.
3.1.1 Language of PageSupportsThe root <html lang="en"> is set.
3.2.1 On FocusSupportsFocusing a control does not trigger a change of context.
3.2.2 On InputSupportsChanging a form value does not automatically change context; submission is explicit.
3.3.1 Error IdentificationSupportsValidation errors are conveyed in text, associated via aria-describedby, and surfaced through role="alert" regions.
3.3.2 Labels or InstructionsSupportsInputs, selects, and textareas provide visible labels (or aria-label for compact filters) and required-field indicators.
4.1.1 ParsingSupportsMarkup 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, ValueSupportsNative 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

CriteriaConformance LevelRemarks and Explanations
1.2.4 Captions (Live)Not ApplicableNo live media.
1.2.5 Audio Description (Prerecorded)Not ApplicableNo video content.
1.3.4 OrientationSupportsLayout is responsive and not locked to a single orientation; portrait/landscape both render.
1.3.5 Identify Input PurposeSupportsPersonal-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)SupportsColor 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 TextSupportsLayout uses relative units and clamp() for headings; content reflows when text is zoomed to 200%.
1.4.5 Images of TextSupportsText is rendered as live text, not images.
1.4.10 ReflowSupportsThe 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 ContrastSupportsControl borders, the focus indicator (2px solid navy-light with offset), and status dots provide ≥3:1 contrast against adjacent colors.
1.4.12 Text SpacingSupportsLine-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 FocusSupportsChart 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 WaysSupportsContent is reachable via the persistent role-based navigation and, in list views, via search/filter.
2.4.6 Headings and LabelsSupportsPages use a descriptive <h1> (PageHeader) and section <h2>s; form fields are clearly labeled.
2.4.7 Focus VisibleSupportsA 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 PartsNot ApplicableContent is single-language (English).
3.2.3 Consistent NavigationSupportsThe 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 IdentificationSupportsShared components (badges, buttons, status labels) are used consistently across the app.
3.3.3 Error SuggestionSupportsValidation messages describe the problem and expected input where the cause is known.
3.3.4 Error Prevention (Legal, Financial, Data)SupportsConsequential actions (deletions, decisions) are performed through explicit confirm dialogs and are reversible/auditable where applicable.
4.1.3 Status MessagesSupportsStatus 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)

CriteriaConformance LevelRemarks and Explanations
302.1 Without VisionPartially SupportsSemantic 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 VisionSupportsMeets contrast minimums, supports zoom/reflow to 200%+, and provides a visible focus indicator.
302.3 Without Perception of ColorSupportsInformation is not conveyed by color alone (text labels accompany status color).
302.4 Without HearingNot ApplicableNo audio output.
302.5 With Limited HearingNot ApplicableNo audio output.
302.6 Without SpeechNot ApplicableNo speech input required.
302.7 With Limited ManipulationSupportsPrimary 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 StrengthSupportsNo physical reach/strength requirement; standard pointer/keyboard input.
302.9 With Limited Language, Cognitive, and Learning AbilitiesSupportsConsistent navigation, clear labels, error suggestions, and confirmation of consequential actions.

Chapter 4: Hardware

Not Applicable (software-only web application).

Chapter 5: Software

CriteriaConformance LevelRemarks and Explanations
501.1 Scope / 502 / 503See WCAGAs a web application, software requirements are satisfied through the WCAG 2.1 A/AA report above (per 508 §504.2 / E207.2).
504 Authoring ToolNot ApplicableThe product is not an authoring tool for third-party content.

Chapter 6: Support Documentation and Services

CriteriaConformance LevelRemarks and Explanations
602.2 Accessibility and Compatibility FeaturesSupportsA 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 DocumentationSupportsIn-app legal/support pages and the Accessibility Statement are standard accessible HTML, linked from the site footer.
603 Support ServicesSupportsSupport 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:

  1. 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).
  2. 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 is aria-hidden so the data is announced exactly once (1.1.1).
  3. Clickable non-native elements made keyboard-operable — the shared Card, inspection-record rows, reservations-table rows, and the desktop schedule appointment blocks now expose role="button", are focusable (tabIndex=0), and activate on Enter/Space, with descriptive aria-labels (2.1.1 / 4.1.2).
  4. Input purpose identified — an autoComplete/inputMode prop was added to the shared Input primitive and wired to the account name, booking phone, and service-address fields; auth fields already carried the correct tokens (1.3.5).
  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).
  6. Automated accessibility gate added@axe-core/playwright scans 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.
  7. 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 vitest suite (src/test/a11y/). This surfaced and fixed an unlabeled sort <select> and date filter in the reservations table (4.1.2 / 1.3.1).
  8. Per-route page titles — dashboard and authentication routes now set a descriptive document.title from their heading; legal/marketing routes use Next.js metadata (2.4.2).
  9. 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).
  10. 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.
  11. 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 with aria-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).
  12. 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 native title tooltip, moving 1.4.13 to full Supports (1.4.13).
  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 an aria-hidden wrapper 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.
  14. Manual screen-reader findings addressed — a keyboard/screen-reader pass of the booking flow and admin dashboard prompted a batch of fixes: the shared Select no longer suppresses its focus ring; the two bespoke portal modals (reservation detail, inspection result) gained full dialog semantics and a focus trap via a shared useModalA11y hook; 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).
  15. 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 explicit aria-label to the department filter <select>s that had relied on a title tooltip 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.