Date: 9 April 2026 · Scope: Entire app (CSS, 48 components, all pages, context/utils) · Standard: WCAG 2.1 AAA (with AA mandatory)
| Severity | Count |
|---|---|
| Critical | 8 |
| Major | 33 |
| Minor | 38 |
| Total | 79 |
- WCAG: 2.4.7 Focus Visible
- Location:
globals.csslines 729–736 - Issue:
outline: none !importantremoves focus rings from all inputs/textareas/selects, but no replacement border or box-shadow focus style is defined in the global CSS. Any raw input outside a shadcn wrapper has zero visible focus. - Fix: Add fallback:
input:focus-visible, textarea:focus-visible, select:focus-visible { outline: none !important; border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring); }
- Done
- WCAG: 1.4.3 Contrast (Minimum)
- Location:
globals.cssline 191 - Issue:
oklch(0.70 0.14 160)onoklch(0.25)card = 4.1:1, below AA 4.5:1. All "savings" and "decrease" indicators are unreadable in dark mode. - Fix: Lighten to
oklch(0.78 0.14 160). - Done
- WCAG: 2.1.2 No Keyboard Trap / 4.1.2 Name, Role, Value
- Location:
SearchCommand.tsxlines 278–350 - Issue: No
role="dialog", noaria-modal, no focus trap. Keyboard users tab behind the overlay. - Fix: Add
role="dialog" aria-modal="true" aria-label="Search councils". Implement focus trap. Store trigger ref, restore focus on close. - Done
- WCAG: 2.1.2 / 4.1.2
- Location:
FeatureRequestDialog.tsxlines 79–176 - Issue: Custom
divoverlay with no dialog semantics, no focus trap, no focus management. - Fix: Add
role="dialog" aria-modal="true" aria-labelledby. Focus trap + auto-focus first input + restore focus on close. - Done
- WCAG: 2.1.2 / 4.1.2
- Location:
DonateButton.tsxlines 86–196 - Issue: Portal modal with no dialog role, no focus trap, no Escape handler.
- Fix: Same pattern as C4.
- Done
- WCAG: 2.1.2 / 4.1.2
- Location:
proposals/ShareButton.tsxlines 88–171 - Issue: Same — no dialog semantics or focus management.
- Fix: Same pattern as C4.
- Done
- WCAG: 4.1.2 / 2.1.1 Keyboard
- Location:
AccountModal.tsxlines 70–133 - Issue: No
role, noaria-haspopup, focus not managed on open/close. - Fix: Add
role="menu"orrole="dialog",aria-haspopup="true"on trigger, focus into popover on open. - Done
- WCAG: 4.1.3 Status Messages
- Location:
layout.tsx - Issue: No
aria-liveregion announces page title changes. Screen reader users get no feedback when navigating between routes. - Fix: Create a
RouteAnnouncercomponent usingusePathname()+aria-live="assertive"renderingdocument.title. - Done
- WCAG: 1.4.6 Contrast (Enhanced)
- Location:
globals.cssline 109 - Fix: Darken to
oklch(0.38 0 0)for ~7.5:1. - Done
- WCAG: 1.4.6 Contrast (Enhanced)
- Location:
globals.csslines 100, 109 - Fix: Same as M1 — darkening
muted-foregroundfixes both. - Done
- WCAG: 1.4.3 Contrast (Minimum)
- Location:
globals.cssline 124 - Fix: Add comment
/* DECORATIVE ONLY — do not use as text on light backgrounds */or darken significantly if used as text. - Done
- WCAG: 1.4.6 Contrast (Enhanced)
- Location:
globals.cssline 125 - Fix: Darken to
oklch(0.55 0.10 250)for AAA text use. - Done
- WCAG: 1.4.3 Contrast (Minimum)
- Location:
globals.cssline 171 - Fix: Lighten to
oklch(0.80 0.12 250). - Done
- WCAG: 1.4.11 Non-text Contrast
- Location:
globals.cssline 174 - Fix: Increase to
oklch(1 0 0 / 25%). - Done
- WCAG: 1.4.3 Contrast (Minimum)
- Location:
globals.cssline 137 - Fix: Darken to
#9a6520or ensure only used as background, never as text. - Done
- WCAG: 4.1.2 Name, Role, Value
- Location:
SearchCommand.tsxline 287 - Fix: Add
aria-label="Search councils by name or postcode". - Done
- WCAG: 4.1.2
- Location:
CouncilSelector.tsxlines 281, 339, 399 - Fix: Add
aria-label="Search councils by name or postcode"to each. - Done
- WCAG: 4.1.2
- Location:
CouncilSwitcher.tsxline 48 - Fix: Add
aria-label="Switch council". - Done
- WCAG: 1.3.1 Info and Relationships
- Location:
DisplayNamePrompt.tsxline 56 - Fix: Add
aria-label="Display name"or associate withhtmlFor/id. - Done
- WCAG: 1.3.1
- Location:
DonateButton.tsxline 147 - Fix: Add
id="custom-donation-amount"to input,htmlFor="custom-donation-amount"to label. - Done
- WCAG: 4.1.2
- Location:
SearchCommand.tsx,CouncilSelector.tsx - Fix: Add
role="listbox"on results container,role="option"+aria-selectedon items,aria-activedescendanton input. - Done
- WCAG: 4.1.2
- Location:
TaxBandsCard.tsxline 58 - Fix: Add
role="radiogroup" aria-label="Select council tax band"on container,role="radio" aria-checkedon each button. - Done
- WCAG: 1.3.1 / 4.1.2
- Location:
proposals/page.tsxline 279 - Fix: Add
aria-label="Filter by budget area". - Done
- WCAG: 1.4.4 Resize Text
- Location:
globals.cssline 458 - Fix: Increase to
0.75rem(12px) minimum. - Done
- WCAG: 1.4.4
- Location:
globals.cssline 639 - Fix: Increase to
0.8125rem(13px) minimum. - Done
- WCAG: 1.4.4
- Location:
globals.cssline 601 - Fix: Keep at
0.875rem(14px), same as table base. - Done
- WCAG: 2.5.5 Target Size (Enhanced)
- Location:
globals.csslines 272–281 - Fix: Narrow exceptions to
p a, span a, li a, .badge, [data-slot="badge"]only. Remove.flex a,.flex button,.grid button. - Done
- WCAG: 2.5.8 Target Size
- Location:
shareable-stat.tsxline 90 - Fix: Increase to
w-11 h-11(44px). - Done
- WCAG: 2.5.8
- Location:
status-panel.tsxline 59 - Fix: Add
min-h-[44px] min-w-[44px]. - Done
- WCAG: 2.5.8
- Location:
VoteButton.tsxline 112 - Fix: Increase to
w-11 h-11. - Done
- WCAG: 2.5.8
- Location:
CommentThread.tsxline 184 - Fix: Add
py-2 px-2padding ormin-h-[44px]. - Done
- WCAG: 2.5.8
- Location:
ShareButton.tsxline 301 - Fix: Increase to
h-11. - Done
- WCAG: 2.5.8
- Location:
Footer.tsxlines 27–48 - Fix: Add
min-h-[44px] inline-flex items-center. - Done
- WCAG: 2.5.8
- Location:
DataSourcesFooter.tsxline 123 - Fix: Add
min-h-[44px]. - Done
- WCAG: 3.1.1 Language of Page
- Location:
layout.tsxline 48,global-error.tsxline 17 - Fix: Change to
lang="en-GB". - Done
- WCAG: 3.2.3 Consistent Navigation / 2.4.1 Bypass Blocks
- Location:
data/page.tsx,compare/[matchup]/page.tsx, 4 insights subpages,leaderboards/page.tsx, 4 guide pages - Fix: Add
<Header />and<Footer />imports, or create a shared layout. - Done
- WCAG: 1.3.1 Info and Relationships
- Location:
about/page.tsxlines 87, 96, 105, 114 - Fix: Change
<h4>to<h3>. - Done
- WCAG: 2.4.2 Page Titled
- Location:
auth/login/page.tsx,donate/thank-you/page.tsx - Fix: Add layout.tsx with metadata or dynamic
document.title. - Done
- WCAG: 4.1.3 Status Messages
- Location:
loading.tsx(root),council/[slug]/loading.tsx,proposals/loading.tsx - Fix: Add
role="status"and<div class="sr-only">Loading...</div>. - Done
- WCAG: 4.1.3 Status Messages
- Location:
council/[slug]/error.tsx,proposals/error.tsx - Fix: Add
role="alert"to error content container. - Done
- WCAG: 1.4.1 Use of Color
- Location:
compare/[matchup]/page.tsxline 221 - Fix: Add checkmark icon or textual label alongside color.
- Done
Decorative icons missing aria-hidden (10)
- Header logo
<Landmark>icon —Header.tsx:92 - Mobile hamburger icons (small breakpoint) —
Header.tsx:168 - LeadershipCard
<User>icons —LeadershipCard.tsx:39,51,82 - ContributeBanner
<Heart>icon —ContributeBanner.tsx:12 - DonateButton
<Heart>icons —DonateButton.tsx:100,179,185 - Search icon in overlay —
SearchCommand.tsx:286 - CouncilSelector search icons —
CouncilSelector.tsx:280,338,393 - Sonner toast icons —
sonner.tsx:20–25 - About page section icons —
about/page.tsx:36,73,84,125 - CheckCircle list bullet icons — About, Accessibility, Updates, Methodology pages
- About page data source links —
about/page.tsx:134–169 - Privacy page external links —
privacy/page.tsx:59,107,149,163 - License page GitHub link —
license/page.tsx:200 - Methodology page source links —
methodology/page.tsx:185–236
- Expand to cover
--positive,--negative,--accent-foreground,--destructive,--chart-*in both light and dark —globals.css:756–766
- Add
@media (forced-colors: active) { :focus-visible { outline: 3px solid Highlight; } }—globals.css:769
- Consider bumping to 14px —
globals.css:450
- Add
@media (prefers-reduced-motion: reduce) { .card-elevated-interactive:hover { transform: none; } }—globals.css:317
- Increase to
oklch(0.30 0.006 250)—globals.css:506
- Consider adding
.reduce-motionclass toggle — systemic
- Gate hover utilities for touch devices — systemic
- Add
aria-expanded={mobileMenuOpen}—Header.tsx:146,160
Floating nav focusable when hidden
- Add
inertorvisibility: hiddenwhen!isScrolled—Header.tsx:226
- Increase to
h-11 w-11—Header.tsx:340
- Add
role="progressbar" aria-valuenow aria-valuemin aria-valuemax—MilestoneBar.tsx:18
- Add
aria-pressed={labels.includes(l.value)}—ProposalForm.tsx:325
- Add to last item —
Breadcrumb.tsx:33
- Add
aria-label="Write a comment"—CommentForm.tsx:125
- Add
aria-live="polite"to results containers —CouncilSelector.tsx,SearchCommand.tsx
- Add
role="alert"—auth/login/page.tsx:105
- Promote subsection h3s to h2 where appropriate
- Add
aria-liveannouncement when signed out —AuthContext.tsx
- Add
<main>wrapper —embed/council/,embed/[id]/
- Leaderboards —
leaderboards/page.tsx:97 - Compare —
CompareClient.tsx:306
- Service quality ratings —
ServiceOutcomesCard.tsx:157 - KPI status dots —
ServiceOutcomesCard.tsx:270 - YourBillCard precept bar —
YourBillCard.tsx:94
- Add
role="img" aria-labeloraria-hidden="true"—BillHistoryCard.tsx:57
- Consider
<h3>default for CardTitle,<p>for CardDescription —card.tsx:31,41
- Remove from natively focusable input —
SearchCommand.tsx:300
- Add
role="group" aria-label="Sort proposals"+aria-pressed—proposals/page.tsx:259
- Add
aria-live="polite"to proposals list container —townhall/page.tsx:123,proposals/page.tsx:292
- Configure Sonner
toastOptionsfor error assertiveness
- External link
sr-onlytext used consistently across dashboard cards, Footer, DataSourcesFooter aria-expanded+aria-controlson SpendingCard, SuppliersGrantsCard, PayAllowancesCard- AlertDialog (Radix) provides proper focus trapping and keyboard handling
- ThemeToggle has dynamic
aria-labelbased on current state - ProposalForm uses
aria-describedby,aria-invalid,aria-live="polite"for validation prefers-reduced-motionmedia query disables all animations globallyprefers-contrast: moreexists (needs expansion)forced-colors: activepartial support exists- Skip link present in root layout
- All pages have
<main id="main-content">