A full accessibility pass over apps/web (every source file read; WCAG 2.1 AA + ARIA APG) produced 62 findings. The ARIA-correctness layer is already addressed in PR #154 and PR #155, and a follow-up PR covers the mechanical remainder. This issue holds the findings that are real WCAG failures but carry a visual, content, or product dimension that shouldn't be decided unilaterally by a volunteer — each needs a maintainer call (and, per the specops workflow, a spec touch where noted).
1. Green Volunteer CTA fails contrast — Serious (WCAG 1.4.3)
White on green-600 (#16a34a) ≈ 3.05:1; normal-size text needs 4.5:1. It's the site's primary CTA in three places. green-700 is borderline (≈ 4.5:1); green-800 passes comfortably. A one-class change — but it's the brand green, and there's no success button variant or green token, so this magic color is also a design-system gap.
AppHeader.tsx · Home.tsx:92 · Volunteer.tsx:37
2. Links distinguished by color alone, and the colors are nearly identical — Serious (WCAG 1.4.1)
--primary and --foreground are both near-black neutrals (≈ 1.4:1 against each other vs the 3:1 required when color is the only differentiator). Some screens add underline, many don't. Either underline inline links by default or give --primary a real hue — a theme decision.
index.css:53,58 · ~20 screens
3. Every route shares one document title — Serious (WCAG 2.4.2)
Nothing sets document.title per route; every page is "Code for Philly" to screen readers and tab-switchers. Needs a small per-route title convention (and a spec touch, since screens are spec-governed).
index.html:8 · App.tsx
4. Hero slideshow auto-advances with no pause control — Serious (WCAG 2.2.2)
aria-hidden hides it from AT, but the criterion is about motion in the viewport: auto-advancing (8s crossfades) needs a visible pause/stop affordance.
HeroSlideshow.tsx
5. Whole pages have no headings because CardTitle is a <div> — Serious (WCAG 1.3.1 / 2.4.6)
All account-claim and password-reset pages are built from cards only, so they render zero headings and no <h1>. Fix is per-page heading placement (or a heading-level prop pattern), not necessarily changing the shadcn primitive.
ui/card.tsx:36 · pages/AccountClaim*.tsx · PasswordReset*.tsx
6. Blog/buzz images are unconditionally alt=\"\" — Serious (WCAG 1.1.1)
Authors have no way to supply alt text for featured images. Needs a content-model decision: an alt field on posts/updates, or an explicit editorial policy that featured images are decorative.
BlogDetail.tsx:36 · BlogIndex.tsx:124 · ActivityCard.tsx:80
7. Client-side navigation never moves focus or announces the new page — Moderate (WCAG 2.4.3)
<main tabIndex={-1}> exists but nothing focuses it on route change. A small router-level focus/announce hook — worth pairing with the document-title work (item 3).
AppShell.tsx:24 · App.tsx
8. Filter/search result updates are silent — Moderate (WCAG 4.1.3)
Index screens re-render their lists after the debounce with no status announcement. A shared "N results" role=\"status\" line would cover them; copy and placement are design calls.
ProjectsIndex · PeopleIndex · HelpWantedIndex · TagsNamespace
9. Reduced-motion honored only by the hero's Ken Burns pan — Moderate
Spinners, sheet/dialog transitions, and scrollTo({behavior:'smooth'}) run regardless of prefers-reduced-motion. The usePrefersReducedMotion hook exists and is under-applied; a global @media (prefers-reduced-motion: reduce) block is the usual fix.
index.css · 5 screens' scrollTo · HeroSlideshow.tsx:70
10. Network-error banner says "Retry" but only dismisses — Moderate (spec↔code contradiction)
The spec (specs/behaviors/app-shell.md:162) prescribes [Retry], and the button's handler only calls clearError(). Surfaced during the ARIA pass and recorded in plans/aria-correctness.md follow-ups. Either the button should refetch or the spec's label should change — both need a spec edit first, so it's parked here rather than patched.
NetworkErrorBanner.tsx · specs/behaviors/app-shell.md:162
Happy to implement any of these once there's a direction — most are small once the decision is made.
🤖 Generated with Claude Code
A full accessibility pass over
apps/web(every source file read; WCAG 2.1 AA + ARIA APG) produced 62 findings. The ARIA-correctness layer is already addressed in PR #154 and PR #155, and a follow-up PR covers the mechanical remainder. This issue holds the findings that are real WCAG failures but carry a visual, content, or product dimension that shouldn't be decided unilaterally by a volunteer — each needs a maintainer call (and, per the specops workflow, a spec touch where noted).1. Green Volunteer CTA fails contrast — Serious (WCAG 1.4.3)
White on
green-600(#16a34a) ≈ 3.05:1; normal-size text needs 4.5:1. It's the site's primary CTA in three places.green-700is borderline (≈ 4.5:1);green-800passes comfortably. A one-class change — but it's the brand green, and there's nosuccessbutton variant or green token, so this magic color is also a design-system gap.AppHeader.tsx·Home.tsx:92·Volunteer.tsx:372. Links distinguished by color alone, and the colors are nearly identical — Serious (WCAG 1.4.1)
--primaryand--foregroundare both near-black neutrals (≈ 1.4:1 against each other vs the 3:1 required when color is the only differentiator). Some screens addunderline, many don't. Either underline inline links by default or give--primarya real hue — a theme decision.index.css:53,58· ~20 screens3. Every route shares one document title — Serious (WCAG 2.4.2)
Nothing sets
document.titleper route; every page is "Code for Philly" to screen readers and tab-switchers. Needs a small per-route title convention (and a spec touch, since screens are spec-governed).index.html:8·App.tsx4. Hero slideshow auto-advances with no pause control — Serious (WCAG 2.2.2)
aria-hiddenhides it from AT, but the criterion is about motion in the viewport: auto-advancing (8s crossfades) needs a visible pause/stop affordance.HeroSlideshow.tsx5. Whole pages have no headings because
CardTitleis a<div>— Serious (WCAG 1.3.1 / 2.4.6)All account-claim and password-reset pages are built from cards only, so they render zero headings and no
<h1>. Fix is per-page heading placement (or a heading-level prop pattern), not necessarily changing the shadcn primitive.ui/card.tsx:36·pages/AccountClaim*.tsx·PasswordReset*.tsx6. Blog/buzz images are unconditionally
alt=\"\"— Serious (WCAG 1.1.1)Authors have no way to supply alt text for featured images. Needs a content-model decision: an alt field on posts/updates, or an explicit editorial policy that featured images are decorative.
BlogDetail.tsx:36·BlogIndex.tsx:124·ActivityCard.tsx:807. Client-side navigation never moves focus or announces the new page — Moderate (WCAG 2.4.3)
<main tabIndex={-1}>exists but nothing focuses it on route change. A small router-level focus/announce hook — worth pairing with the document-title work (item 3).AppShell.tsx:24·App.tsx8. Filter/search result updates are silent — Moderate (WCAG 4.1.3)
Index screens re-render their lists after the debounce with no status announcement. A shared "N results"
role=\"status\"line would cover them; copy and placement are design calls.ProjectsIndex·PeopleIndex·HelpWantedIndex·TagsNamespace9. Reduced-motion honored only by the hero's Ken Burns pan — Moderate
Spinners, sheet/dialog transitions, and
scrollTo({behavior:'smooth'})run regardless ofprefers-reduced-motion. TheusePrefersReducedMotionhook exists and is under-applied; a global@media (prefers-reduced-motion: reduce)block is the usual fix.index.css· 5 screens' scrollTo ·HeroSlideshow.tsx:7010. Network-error banner says "Retry" but only dismisses — Moderate (spec↔code contradiction)
The spec (
specs/behaviors/app-shell.md:162) prescribes[Retry], and the button's handler only callsclearError(). Surfaced during the ARIA pass and recorded inplans/aria-correctness.mdfollow-ups. Either the button should refetch or the spec's label should change — both need a spec edit first, so it's parked here rather than patched.NetworkErrorBanner.tsx·specs/behaviors/app-shell.md:162Happy to implement any of these once there's a direction — most are small once the decision is made.
🤖 Generated with Claude Code