This is a Next.js project bootstrapped with create-next-app.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
First, bootstrap your local development environment:
npm run setup:devThe setup script verifies Node.js and npm, creates a local .env.local from .env.example when needed, installs dependencies with npm ci, and runs lint plus unit tests. Use npm run setup:dev -- --skip-install --skip-checks if dependencies are already installed and you only need env-file scaffolding.
Then, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun devOpen http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
To learn more about Next.js, take a look at the following resources:
- Next.js Documentation - learn about Next.js features and API.
- Learn Next.js - an interactive Next.js tutorial.
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
This section is currently a placeholder. Component documentation will be added here in the future.
This section is currently a placeholder. Styling documentation will be added here in the future.
Utility Protocol is committed to making its operator dashboard usable by everyone, including people who rely on assistive technology.
WCAG 2.1 Level AA. We measure against the wcag2a, wcag2aa, wcag21a, and wcag21aa rule sets.
As of the latest audit, the application has no automated accessibility violations at critical, serious, or moderate impact on the audited routes:
| Route | axe-core (WCAG 2.1 AA) | Keyboard navigation |
|---|---|---|
/ (operator dashboard) |
✅ 0 violations (all severities) | ✅ reachable, no traps |
/export |
✅ 0 violations (all severities) | ✅ reachable |
The dashboard is composed of GracefulDegradationDashboard, SloMonitoringPanel, DisasterRecoveryPanel, and the spatial grid views, all rendered at /.
Testing is automated and runs on demand via Playwright:
npm run test:a11yThis runs two suites in tests/a11y/:
axe-coreaudit (axe.spec.ts) — scans each route against WCAG 2.1 A/AA and fails the build on any critical, serious, or moderate violation.- Keyboard navigation (
keyboard.spec.ts) — verifies header controls are reachable byTab, that tabbing reaches multiple distinct elements without a focus trap, and that the theme toggle is operable by keyboard.
These suites are enforced in CI by .github/workflows/a11y.yml, which runs them via playwright.a11y.config.ts on every change, so an accessibility regression fails the build. npm run test:a11y runs the same suites locally.
lang="en"set on the root<html>element (src/app/layout.tsx).- Semantic, native interactive elements (buttons, selects) with ARIA names — no non-semantic
<div onClick>interactive controls. - Visible focus indicators (
:focus-visible/ focus-ring styles). - Theme system with light/dark modes chosen for adequate contrast.
- Loading skeletons carry visible text rather than being empty regions.
- Manual screen-reader verification (VoiceOver / NVER) is not automated; automated tooling cannot fully substitute for it. Reviewers should spot-check new interactive panels with a screen reader.
- Map / canvas-based visualizations (
GridMap,FleetGrid) convey spatial data that is inherently visual; they are accompanied by textual data elsewhere on the page, but a fully equivalent non-visual experience is an ongoing effort.
If you encounter an accessibility barrier, please open an issue on the repository with the route, the assistive technology used, and a short description. Accessibility regressions should be reported the same way and are treated as bugs.
Audit Date: 2026-07-25 Scope: Utility Protocol Frontend Standard: WCAG 2.1 AA (50 success criteria across 4 principles) Tools: axe-core 4.9, NVDA 2024, VoiceOver, manual keyboard inspection
| Principle | Criteria | Pass | Fail | Not Applicable |
|---|---|---|---|---|
| Perceivable | 29 | 24 | 3 | 2 |
| Operable | 24 | 18 | 4 | 2 |
| Understandable | 20 | 18 | 2 | 0 |
| Robust | 5 | 4 | 1 | 0 |
| Total | 78 | 64 | 10 | 4 |
- Status: Partial Fail
- Location: Map component markers, icon-only buttons in dashboard
- Remediation: Add
altattributes to all<img>elements. Addaria-labelto all icon-only<button>elements. - Estimate: 2h
- Status: Fail
- Location: Data tables in
/opspages lack proper<th>scope and associations - Remediation: Add
<thead>,<th scope="col">, andscope="row"to all data tables. - Estimate: 3h
- Status: Fail
- Location: Tariff slider uses mouse-only
onDrag; map panning requires pointer - Remediation: Implement
useKeyboardNavigationhook for slider. Add arrow-key map navigation. - Estimate: 4h
- Status: Fail
- Location: Mobile navigation menu places close button after menu items in DOM
- Remediation: Reorder DOM so close button precedes focusable items in the menu.
- Estimate: 1h
- Status: Fail
- Location: Global
outline: noneon interactive elements; custom focus styles missing - Remediation: Replace with
focus-visiblepolyfill using2px solid #4A90D9outline. - Estimate: 2h
- Status: Partial Fail
- Location: Muted foreground (#737373) on white (#ffffff) = 4.0:1 (below 4.5:1)
- Remediation: Update
--muted-foregroundto #595959 (4.7:1). - Estimate: 0.5h
- Status: Fail
- Location: Dashboard grid uses fixed
min-width: 1200pxpreventing 200% zoom - Remediation: Replace with responsive grid using
minmax(280px, 1fr). - Estimate: 2h
- Status: Fail
- Location: Custom select components lack
role="combobox"andaria-activedescendant - Remediation: Add proper ARIA attributes to custom form controls.
- Estimate: 3h
- Status: Fail
- Location: Toast notifications and meter status updates not announced by screen readers
- Remediation: Wrap dynamic messages in
aria-live="polite"regions. - Estimate: 1h
- Status: Fail
- Location: Shake-to-refresh gesture on mobile has no non-motion alternative
- Remediation: Add refresh button alongside gesture.
- Estimate: 0.5h
- Apply accessible theme palette via
theme.ts - Add global focus indicator styles via
focus.ts - Create
AriaLiveRegioncomponent for dynamic announcements - Implement
useKeyboardNavigationhook
- Add ARIA landmarks and labels to all pages
- Add proper table semantics to data tables
- Add
aria-labelto icon-only buttons - Replace
outline: nonewith focus-visible styles
- Responsive grid for dashboard
- ARIA combobox for custom selects
- Shake-to-refresh alternative
- CI integration with axe-core
- Tab Order: 4 violations found (fixed in Phase 1)
- Focus Trapping: 1 violation in modals (fixed)
- Arrow Key Navigation: Slider and map tab panel not keyboard-operable (Phase 1)
- Dynamic Updates: Not announced (Phase 1 — AriaLiveRegion)
- Form Errors: Not associated with inputs (Phase 2)
- Landmarks: Missing
<nav>and<main>landmarks (Phase 2)
- Same issues as NVDA plus:
- Data Tables: Row/column headers not announced (Phase 2)
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.