# SchoolCompare UX/UI Audit — 2026-07-02 ## Method summary Journey-led walk-through of the **live site** (schoolcompare.co.uk) via Playwright browser tools, at two viewports: **390×844 mobile (primary — 56% of traffic)** and **1440×900 desktop** (720×450 used as the 200%-zoom reflow proxy). Five journeys in traffic order — home→find-school (63% of entries), cold landing on a school page (SEO long tail), building a comparison (27% of views), rankings→shortlist (12%), admissions (5%, light pass) — followed by a cross-cutting cohesion pass comparing computed typography, colour, and component styles across all six page types, verified against `nextjs-app` source to distinguish "token exists but bypassed" from "no token exists". Every page state visited was scanned with **axe-core 4.10.2** (WCAG 2.2 A/AA rule set) plus manual checks: touch targets ≥24px (2.5.8), keyboard order and focus visibility, and 200% reflow. A finding is included only if it cites a Nielsen heuristic, a WCAG 2.2 AA failure, a mobile-usability standard, or an observed task-flow obstruction — no taste-only findings. Priorities are traffic-weighted using the 30-day analytics baseline: entries `/` 63% / `/compare` 20% / `/rankings` 6%; exits `/` 46% / `/compare` 32% / `/rankings` 13%; 56% mobile. Uplift bands are honest small/moderate/large indications — there is no funnel instrumentation to support percentages. Full evidence trail: `docs/superpowers/specs/2026-07-02-ux-audit-notes/` (six notes files; screenshots referenced by filename live in the session scratchpad, not committed). ## What works today — keep These are strengths the fixes below must not regress: - **Name search is genuinely excellent.** "Welland Primary" → the correct school as sole top result, in 3 taps with instant loads — comfortably under the 15s target. Result cards are information-rich (Ofsted grade+year, RWM % with trend arrow, "+12 pts vs national", pupil count, LA) and adapt correctly to phase (Attainment 8 for secondary, RWM for primary). - **Search is the unmistakable primary action** — above the fold on both viewports, 70px tap target, plain-English placeholder, content order puts the task first. - **The primary (KS2) school page explains its data in plain English** — "End-of-primary-school tests taken by Year 6 pupils", the in-place "Why is combined lower?" explainer, legends on subject charts. This is the model the secondary template should copy. - **Everything is anchored to the national average** ("+14 pts", "National avg 39.1") — a non-specialist can judge good/bad without leaving the page. - **Graceful, honest missing-data handling** — COVID-cancellation notes, "contact the admissions authority" for absent cut-offs; no broken or empty UI anywhere data is missing. - **Ofsted sections are clear, current, and sourced** — grade + inspection date, the post-Sept-2024 grading-change note, link to the real Ofsted report. - **Compare's empty state is instructive and actionable** — "No schools selected" + "+ Add Schools to Compare" opening an in-page search modal; the 20% who enter cold can start immediately. The modal supports efficient multi-add (stays open, clears field) and accepts postcodes. - **State persistence is strong across the site** — compare selections survive navigation (localStorage) and are URL-encoded/deep-linkable; rankings restores filter, metric, *and* scroll position on back-navigation (the classic mobile task-killer check, passed cleanly). - **Compare's metric selector carries a plain-English caption** — jargon defined at point of use; the chart legend labels series by colour *and* name (WCAG 1.4.1 satisfied). - **Accessibility fundamentals are partly in place** — working skip link, logical tab order everywhere, strong 2px orange focus outlines on links/buttons, real `` semantics on rankings, properly labelled rankings filters, clean heading hierarchy on admissions, no horizontal scroll at 200% reflow on any page tested. - **Admissions content is well-integrated** — reachable in one tap from anywhere, surfaced proactively on the homepage, with 12 stage-tailored cross-links into Search/Compare/Rankings that read as genuinely contextual. - **Cohesion anchors:** header/footer pixel-identical on all six pages; coral=primary / teal=secondary held without role collisions; rankings and compare share one phase-tab treatment; the deadline chip is byte-for-byte reused between home and admissions. ## Findings ### P0 — Urgent - **P0.1 — Colour-contrast failures on every main route, including primary data and primary CTAs** *(merges J1-F4, J2-F4, J3-F6, J4-F1, J5-F1)* - Evidence: axe `color-contrast` (serious) on **all five routes**: home 6 nodes (active nav tab, `.btn`, how-it-works text) + 3 on results (`.btn-primary`, Ofsted badge); school template **13 nodes** (Back button, "View on map", active metric tab); compare 5 nodes (`.btn-primary`, active phase/nav tabs); rankings **100 nodes on desktop** — the teal metric values (`.valueCell strong`) in every row plus "+ Compare" links; admissions 13 nodes including the **deadline countdown chips**. Screenshots `j4-rankings-desktop.png`, `j5-admissions-mobile.png`. - Criterion: WCAG 2.2 **1.4.3 Contrast (Minimum), AA** — failing on every main page. - Argument: this is not chrome-only. On rankings the failing text is the ranking score itself — the entire reason a parent is on the page; on admissions it is the days-until-deadline chip — the page's most load-bearing fact; on home/compare it is the primary CTA styling. The mobile-primary audience reads this outdoors on phones, where low contrast bites everyone, not just low-vision users. - Recommendation: adjust the failing token values once (`--accent-coral` on light text pairings, the teal-on-cream value colour, tint-background label colours) and re-run axe on all five routes. This is a token-level fix, not per-page work. - Uplift: **accessibility compliance — large improvement** (clears the single biggest violation class, present on 100% of audited pages); secondary small positive effect on all engagement metrics via legibility for the 56% mobile share. - **P0.2 — School name (H1) is obscured by the map and the "+ Add to Compare" button on mobile — systemic to the school template** *(J2-F1)* - Evidence: measured bounding boxes on both audited schools — Castle: map bottom 280px / H1 top 272px / compare-button top 286px / H1 bottom 309px; Welland: same pattern — leaving a ~6px legible sliver of the name. Screenshots `j2-school-mobile-fold.png`, `j2-welland-mobile-full.png`. Desktop is unaffected. - Criterion: Nielsen #1 (visibility of system status) and #8; task-flow obstruction of cold-landing orientation; defeats the intent of WCAG 1.4.10 (content obscured at the primary viewport). - Argument: school pages are the SEO landing template. A parent arriving from Google asks one question first — "is this the school I searched for?" — and the element that answers it is illegible on the majority viewport. This is the exact first-screen failure that produces an immediate back-to-Google bounce, on the template whose entire job is converting search traffic. - Recommendation: restack the mobile hero so map, H1, and compare button occupy non-overlapping space (e.g. compare button below the identity block or docked, map height capped above the H1). One template, both school-detail components (see P2.6). - Uplift: **school-page bounce-back-to-Google — decrease, moderate** (fixes the first-glance orientation on every SEO landing; the page content behind it is already strong, so retention should follow). - **P0.3 — Postcode search returns a distance-less, unsorted, mixed-phase list** *(J1-F2)* - Evidence: `/?search=B91+3` → "8 schools found", sort "Relevance", **no distance on any card**, top result a Secondary school, phases interleaved (secondary/independent/primary/sixth-form) — behaviour consistent with a text match on the postcode column rather than a geographic radius search. Screenshot `j1-postcode-results-mobile.png`. The geolocation "Schools near me" button proves proximity machinery exists but the typed-postcode path isn't wired to it. - Criterion: Nielsen #2 (match between system and the real world) + task-flow obstruction on the highest-traffic path. The placeholder explicitly invites "School name **or postcode**". - Argument: a parent typing their postcode expects nearest-first with distances. What they get looks broken — no way to judge which school is closest, led by the wrong phase. Home is 63% of entries and 46% of exits; this is the largest observed task-level leak feeding that exit rate, on the discovery path used by exactly the parents who *don't* already know a school name. - Recommendation: detect postcode-shaped queries, geocode (postcodes.io is already integrated), sort by distance, and show "X.X miles" on each card — i.e. route the postcode text path through the same logic as "Schools near me". Default the phase filter sensibly or surface it prominently on postcode results. - Uplift: **home 46% exit rate — decrease, moderate-to-large** (repairs the primary discovery task for the know-my-area-not-my-school segment; large band justified because the failure currently reads as "site is broken" rather than "site lacks a feature"). ### P1 — High - **P1.1 — Filter and Sort selects on home results have no accessible name** *(J1-F3)* - Evidence: axe `select-name` (impact **critical**, 2 nodes) on `/?search=…` — the phase filter and sort selects. (Borderline P0 by the "clear WCAG failure on a main page" rule; held at P1 because it affects one page state and two controls.) - Criterion: WCAG 2.2 **4.1.2 Name, Role, Value, Level A**. - Argument: these are the controls a parent uses to narrow a mixed result set to "Primary" — a screen-reader user hears two anonymous comboboxes on the main journey's results view. Rankings labels its identical selects correctly, so this is an omission, not a pattern. - Recommendation: associate visible `
` cells lack `scope`/`aria-sort` (WCAG 1.3.1 best practice). Uplift: rankings→school CTR, small. - **P3.6 — 33-option metric dropdown is a flat jargon list** *(J4-F6)* — GPS/EAL/SEN abbreviations, ungrouped (Nielsen #6). Group by theme (Results / Progress / Cohort) with plain-language labels. Uplift: rankings→school CTR, small. - **P3.7 — Home deadline rail defaults its scroll to the least-urgent card** *(J1-F5(b))* — the nearest deadline (121 days) hides behind less urgent ones (Nielsen #1). Order/scroll to soonest-first. Uplift: home exit, small. - **P3.8 — School trend chart has no legend for the national series; badge overlaps a heading** *(J2-F6)* — the grey dashed line is unexplained (the primary page's charts *do* carry legends — inconsistent); "Nat avg 39.1" overlaps the section heading. Uplift: school-page bounce, small. - **P3.9 — Residual colour/type token drift** *(cohesion F6, F7, F8)* — pressed-coral exists as three hexes (`#e07256`/`#c45a3f`/`#d4654a`), an off-token gold `#b8920e` on the school-page cross-link badge, H3s render in body font while H2s are Playfair, and school-detail tables use an 11/13px type scale vs 12/15px on rankings/compare. Fold into the P2.7 token pass. Uplift: school-page bounce-back-to-Google, decrease, small. ## Accessibility summary Axe-core 4.10.2, WCAG 2.2 A/AA rule set. Desktop serves the same DOM/CSS, so mobile results apply at both viewports unless noted. | Page / state | Viewport | Violations | Detail | |---|---|---|---| | `/` (initial) | 390×844 | 2 | `color-contrast` (serious, 6 nodes); `scrollable-region-focusable` (serious, 1) | | `/?search=Welland+Primary` | 390×844 | 2 | `color-contrast` (serious, 3); **`select-name` (critical, 2)** | | `/school/…castle` (secondary) | both | 2 | `color-contrast` (serious, 13); `role-img-alt` (serious, 1) | | `/school/…welland` (primary) | 390×844 | 1 | `role-img-alt` (serious, 1) | | `/compare` (empty) | 390×844 | 1 | `color-contrast` (serious, 2) | | `/compare?urns=…` (2 schools) | 390×844 | 3 | `color-contrast` (serious, 5); `role-img-alt` (serious, 1); `scrollable-region-focusable` (serious, 1) | | `/rankings` (default & filtered) | 390×844 | 1 | `color-contrast` (serious, 2) | | `/rankings` | 1440×900 | 1 | `color-contrast` (serious, **100 nodes** — metric values in every row) | | `/admissions` | 390×844 | 1 | `color-contrast` (serious, 13 — incl. deadline chips) | **Manual checks:** Touch targets ≥24px — pass on all core paths (smallest: compare × at 28px, Back at 64×28) **except** two admissions CTAs at 21px (P2.8). Keyboard — order logical everywhere with a strong 2px outline on links/buttons; gaps: unlabelled home-results selects (P1.1), modal focus loss (P2.4), suppressed outlines on filter selects and the faint search-input ring (P3.4), keyboard-unreachable scroll regions (P1.5). 200% reflow — **pass on every page tested** (no horizontal scroll at 720px; tables retain contained scroll, a recognised 1.4.10 exception). **Overall WCAG 2.2 AA posture:** not currently conformant. Four violation classes account for everything axe found — contrast (every route), missing chart text alternatives (Level A), keyboard-inaccessible scroll regions (Level A), unlabelled selects (Level A, critical) — plus manual findings on target size, focus appearance, and dialog semantics. All are pattern-level fixes; none require redesign. The foundations (skip links, tab order, semantics, reflow, labels on rankings) are solid, which is why the fix list is short relative to the violation node counts. ## Suggested implementation sequence Batches ordered by uplift-per-effort; each is a plausible standalone follow-up project. **Batch 1 — WCAG compliance sweep (small effort, large compliance uplift).** P0.1 contrast token fixes; P1.1 label the two selects; P1.4 chart `aria-label`s via one shared wrapper; P1.5 `tabindex`/role on the three scroll wrappers; P2.4 dialog semantics + focus restoration; P2.8 42px CTAs on admissions; P3.4 standard focus outlines. Mostly CSS-variable and attribute changes; re-run the axe harness on all five routes as the acceptance check. Clears every axe violation class found. **Batch 2 — School-template mobile fold (small effort, direct SEO-retention uplift).** P0.2 un-overlap the H1/map/compare button; P2.5 scrollable section nav; P3.8 trend-chart legend + badge overlap; P3.3 add-confirmation live region. One template, all SEO landings. **Batch 3 — Location search done right (moderate effort, biggest home-funnel lever).** P0.3 postcode → geocoded, distance-sorted, distance-labelled results (reuse the "Schools near me" machinery); P1.6 autocomplete on the home search (reuse the compare modal's live search); P1.7 mobile value-prop line. Together these target the 46% home exit rate from three directions. **Batch 4 — Discovery and sharing bridges (moderate effort, compare-funnel uplift).** P1.2 "Schools nearby" module on school pages (feeds compare); P1.3 URL-over-localStorage precedence for shared links; P2.2 mobile +Compare on rankings rows; P3.1/P3.2 compare state-clarity nits. **Batch 5 — Comprehension pass (small-moderate effort).** P2.3 reuse `/compare` metric definitions on school pages and default rankings to "expected standard"; P2.1 mobile table layouts (sticky first column / stacked metrics); P3.5/P3.6 rankings sort affordance + grouped metric picker; P3.7 deadline-rail ordering. **Batch 6 — Cohesion & token enforcement (housekeeping, protects velocity).** P2.6 dedupe `.btn`, converge the two school-detail components; P2.7 radius/title/segmented-control tokenisation; P3.9 residual colour/type drift. Best done after Batches 1–2 so the new token values land once.