15 KiB
Journey 3: Building a comparison (/compare) — audit notes
Pages visited: /compare (empty state, direct entry), /compare?urns=… (populated, 1–3 schools), add-school modal, /school/142161-… (persistence round-trip)
Viewports: 390×844 (primary), 1440×900 (+720×450 proxy for 200% reflow)
Site: live https://schoolcompare.co.uk only. Read-only: searched, added/removed schools in the compare basket (client/localStorage + URL state), opened/closed the modal, hovered the chart. Submitted no data-modifying forms. selectedSchools was cleared from localStorage only to reproduce a genuine empty state and to test the share-link defect.
Task attempt log
Direct entry premise: 20% of sessions enter on /compare, so the empty state is judged as a cold arrival.
Mobile (390×844) — empty state.
- Navigating to
/comparedirectly first showed a persisted prior selection (The Castle School, carried inlocalStorage.selectedSchoolsfrom an earlier session) — the URL self-rehydrated to?urns=136916&metric=…. This is persistence working (see Works well) but means the "empty" state is only seen by first-ever visitors. Cleared storage to capture the true empty state (j3-compare-empty-mobile.png). - Empty state is instructive and actionable: heading "No schools selected", body "Add schools from the home page or search to start comparing", and a primary button "+ Add Schools to Compare" that opens an in-page search modal. A cold arrival can start without leaving the page.
Mobile — building a two-school comparison. Taps counted from empty state:
- Tap "+ Add Schools to Compare" → opens modal, auto-focuses the "School name or postcode" search box.
- Type "St Mary" → live result list appears (name + LA + school type + "+ Compare" per row).
- Tap "+ Compare" on a result → school added; modal stays open, field clears ("Comparing 1 school" behind it).
- Type "Ottery St Mary" → result list.
- Tap "+ Compare" → second school added (
?urns=142161,113105). - Tap "Close modal" → reveals the comparison (
j3-compare-two-schools-mobile.png).
→ 6 distinct interactions (4 taps + 2 text-entry sequences) to a two-school comparison. Critically, both schools had to be found by name/postcode — there is no browse / "schools near this one" / "similar schools" on-ramp inside compare (ties to Journey 2 F2). A parent who knows only one school name cannot build a comparison here.
Mobile — comparison output assessment.
- Per-metric school cards stack vertically and are fully legible; each shows name, LA, type, the selected metric value (e.g. 77.0% vs 51.0%), colour-keyed to the chart. Good on 390px.
- Chart ("Performance Over Time"): renders on real scroll (blank in full-page captures = lazy-render artifact, confirmed by element screenshot
j3-compare-chart-mobile.png). Legend labels each line by colour + full school name — colour is not the sole differentiator (WCAG 1.4.1 satisfied).canvashasrole="img"butaria-label= null (no text alt). - "Detailed Comparison" table: wrapper 626px inside a 324px column → only Year + the first school column fit; the second school is off-screen behind horizontal scroll (
overflow-x:auto, no visible scroll affordance). The whole point of the table (side-by-side) can't be seen at once on the primary viewport. - Metric selector carries a plain-English caption ("% meeting expected standard in reading, writing and maths") — jargon defined in place (the thing school-detail pages lacked, Journey 2 F3).
Mobile — remove / add / persistence.
- Remove (×) is immediate: no confirm, no toast, no undo; URL updates instantly.
- Postcode search works: "TA1 5AU" → The Castle School (advertised feature, functional).
- Add beyond two / phase mixing: adding a secondary school to a primary comparison keeps the header at "Comparing 2 schools" but renders only the primary card; the secondary sits hidden behind a "Secondary (1)" tab with no explanation.
- Persistence: navigated to a school detail page, then back to bare
/compare— both schools restored and URL rehydrated to?urns=142161,136916. State survives real navigation via localStorage; comparisons are URL-encoded / deep-linkable.
Desktop (1440×900).
- Table fits with both/all school columns visible (no page overflow). Layout clean (
j3-compare-two-schools-desktop.png). - Keyboard add/remove: "+ Add School" reachable and Enter-activatable; modal opens with focus moved into the search input; Tab reaches a result's "+ Compare" with a visible 2px solid orange (#E07256) focus outline; Enter adds it. But after adding, focus drops to
document.body; after Escape (which does close the modal) focus is again onbody, not returned to the "+ Add School" trigger. - Modal has no
role="dialog"and noaria-modal— not announced as a dialog. - Chart tooltip (
j3-compare-chart-tooltip-desktop.png): hover shows year + per-school values with colour swatch + name — mouse-only (no touch/keyboard equivalent), but the same values are present as text in the table below. - 200% zoom (720×450 proxy): no page-level horizontal overflow — content reflows (WCAG 1.4.10 pass); the detailed table retains its own contained horizontal scroll.
Is 32% of exits task-complete satisfaction or abandonment?
Mixed, with concrete abandonment drivers — it is not safe to read the exits as pure satisfaction.
- Points to satisfaction: compare is a terminal "results" tool — the natural next step (visit/apply) is off-site, so a high exit share is partly expected; persistence + shareable URLs mean some exits are "saved for later"; the core output (cards + chart + table) renders and metrics are explained.
- Points to abandonment: (a) 20% ENTER on
/compare, often via a shared link — and the localStorage-overrides-URL defect (F4) silently shows a returning recipient their own schools instead of the shared ones, a confusing dead end; (b) a 2-school comparison needs 6 interactions and prior knowledge of both school names (no discovery on-ramp) — a parent with one school hits a wall; (c) on mobile the detailed table hides the second school off-screen behind an unaffordanced scroll (F1), so the comparison can look incomplete; (d) low-contrast primary buttons (F6) degrade the mobile-daylight path. F1/F4/F6 are each capable of turning an intended task-complete exit into premature abandonment, especially for the 20% arriving on shared links.
Friction points
-
F1. "Detailed Comparison" table hides the second/third school off-screen on mobile
- Evidence: at 390px the table is 626px inside a 324px
.tableWrapper(overflow-x:auto); only Year + the first school column are visible, second column cut off with no scroll affordance (j3-compare-two-schools-mobile.png). - Criterion violated: mobile-usability standard (horizontal scrolling of primary content is a known antipattern) + Nielsen #6 (Recognition rather than recall) — a parent must remember school A's numbers while scrolling to school B; the tool's core "side-by-side" promise is defeated on the primary viewport.
- Argument: the whole reason a parent opens compare is to see schools next to each other; on mobile they can't, and with no scroll cue may believe the second school's data is missing and leave.
- Severity guess: P2 (the stacked metric cards + chart do show both, softening it)
- Evidence: at 390px the table is 626px inside a 324px
-
F2. Comparison table scroll region is not keyboard-accessible
- Evidence: axe
scrollable-region-focusable(serious, 1 node) on.ComparisonView-module__…tableWrapper; the overflowing wrapper has notabindex. - Criterion violated: WCAG 2.2 2.1.1 Keyboard (A).
- Argument: a keyboard-only parent cannot scroll the detailed table to reveal columns beyond the first school — data is literally unreachable without a mouse/touch.
- Severity guess: P2
- Evidence: axe
-
F3. Trend chart exposes no text alternative to assistive tech
- Evidence: axe
role-img-alt(serious) oncanvas;aria-label= null on both mobile and desktop. - Criterion violated: WCAG 2.2 1.1.1 Non-text Content (A).
- Argument: a screen-reader parent gets nothing from "Performance Over Time"; the redundant table softens this but the chart's at-a-glance trend story is lost. Recurs from Journey 2 F5 — systemic to the chart component.
- Severity guess: P2
- Evidence: axe
-
F4. Share link is silently overridden by the visitor's own localStorage
- Evidence: navigating to
/compare?urns=142161,113105whileselectedSchoolsheld a different set resolved the page (and URL) back to the storedurns=142161,136916; only after clearing localStorage did the shared URL load its intended schools. - Criterion violated: Nielsen #1 (Visibility of system status) / consistency; task-flow obstruction of an advertised primary feature ("Share" button).
- Argument: a parent shares their shortlist with a partner; if the recipient has ever used compare, they silently see their own schools with no error or notice — the shared comparison is unreproducible and the collaboration breaks. This directly hits the 20% who enter on
/comparevia links. - Severity guess: P1
- Evidence: navigating to
-
F5. Add-school modal lacks dialog semantics and loses focus on add/close
- Evidence: modal has no
role="dialog"and noaria-modal; after a keyboard "+ Compare" add,document.activeElement=BODY; after Escape-close (Escape does dismiss it), focus is again onBODY, not the "+ Add School" trigger. - Criterion violated: WCAG 2.2 4.1.2 Name, Role, Value (A) + 2.4.3 Focus Order (A).
- Argument: screen-reader users aren't told a dialog opened; keyboard users lose their place after every add (focus jumps to page top) and after closing, so building a multi-school comparison means re-tabbing from the top repeatedly — friction on the add flow every comparison depends on.
- Severity guess: P2 (P1-candidate for keyboard/AT users building 3+ school comparisons)
- Evidence: modal has no
-
F6. Colour-contrast failures on the primary compare controls
- Evidence: axe
color-contrast(serious, 5 nodes populated) — sample targets.btn-primary(the "+ Add School" / "+ Compare" buttons),.…phaseTabActive(active Primary/Secondary tab),.…tabActive .tabLabel(active bottom-nav label). - Criterion violated: WCAG 2.2 1.4.3 Contrast (Minimum) (AA).
- Argument: the exact orange buttons and active tabs a parent must use to build and read a comparison have insufficient text contrast — hard to read on a phone in daylight (the mobile-primary audience). Recurs site-wide (Journey 1/2).
- Severity guess: P1
- Evidence: axe
-
F7. Mixed-phase selection reports "Comparing 2 schools" while showing one
- Evidence: with a primary + a secondary selected, header reads "Comparing 2 schools" but only the primary card/chart/table render; the secondary is hidden behind a "Secondary (1)" tab with no explanatory copy.
- Criterion violated: Nielsen #1 (Visibility of system status) & #2 (Match between system and real world).
- Argument: a parent who added two schools sees one and may think the second was dropped; nothing explains the phase split. (Separating KS2 vs GCSE metrics is itself correct — see Works well — only the count/label is misleading.)
- Severity guess: P3
-
F8. Remove is instant with no undo or confirmation
- Evidence: tapping the × removed a school immediately — no confirm, no toast, no undo; URL updated instantly.
- Criterion violated: Nielsen #3 (User control & freedom — support undo).
- Argument: an accidental tap on the small (28px) × on mobile silently loses a school the parent may have spent a name-search to add; recovery means re-searching. A one-tap "undo" would prevent the loss.
- Severity guess: P3
Works well — keep
- Empty state is instructive and actionable: clear "No schools selected" message plus a "+ Add Schools to Compare" button that opens an in-page search modal — the 20% who enter cold on
/comparecan start immediately (answers Journey 2's "no on-ramp" concern at the compare page itself). - Metric selector carries a plain-English caption for the chosen metric (e.g. "% meeting expected standard in reading, writing and maths") — jargon defined in place, exactly what school-detail pages omitted.
- Search accepts name and postcode (TA1 5AU resolved to the correct school) — the advertised postcode feature works.
- Efficient multi-add: the modal stays open and clears the field after each add, so adding several schools needs no reopen.
- Robust persistence + deep-linking: selection survives real navigation away and back (localStorage), and comparisons are fully URL-encoded/shareable.
- Chart accessibility basics done right for sighted users: legend labels each series by colour and full school name (WCAG 1.4.1 satisfied); desktop hover tooltip gives precise per-year values; the table repeats those values as text.
- Phase separation (Primary/Secondary tabs) sensibly prevents nonsensical KS2-vs-GCSE metric comparisons.
- Desktop keyboard + reflow: visible 2px orange focus outlines on controls; modal opens with focus moved into the search field; Escape dismisses it; all columns visible without overflow; content reflows at 200% zoom with no page-level horizontal scroll (WCAG 1.4.10 pass).
Axe results
/compare(empty) @ 390×844: 1 violation —color-contrast(serious, 2 nodes: active nav tab label,.btn-primary)./compare?urns=142161,113105(populated, 2 schools) @ 390×844: 3 violations —color-contrast(serious, 5 nodes:.btn-primary, active phase tab, active nav tab),role-img-alt(serious, 1 node:canvas),scrollable-region-focusable(serious, 1 node:.tableWrapper).- Desktop 1440×900 populated:
canvasaria-labelstill null (role-img-alt persists);color-contraston.btn-primary/active tabs persists (viewport-independent).
Manual WCAG spot checks
- Touch targets ≥24px on interactive elements (WCAG 2.5.8): pass — "+ Add School" 139×38, Share 110×40, metric select 324×41, phase tabs ~130×40. Smallest is the × remove at 28×28 — above the 24px minimum but the tightest target and below the 44px comfortable norm.
- Keyboard: tab order / focus visibility (desktop): pass with a gap — logical order and visible 2px orange outlines on controls; modal opens with focus moved into the search box; fail on focus restoration — focus drops to
bodyafter an add and after Escape-close rather than returning to a sensible place (F5). - Zoom 200% text reflow (desktop, 720×450 proxy): pass — no page-level horizontal scroll; only the detailed table retains its contained scroll.
Screenshots
j3-compare-empty-mobile.png: true empty state (localStorage cleared) — "No schools selected" + "+ Add Schools to Compare".j3-compare-addmodal-mobile.png: add-school modal with search field.j3-compare-two-schools-mobile.png: mobile 2-school comparison (cards stack; detailed table shows only first school column).j3-compare-chart-mobile.png: element capture proving the chart renders with a colour+name legend.j3-compare-two-schools-desktop.png: desktop 2-school comparison (both columns visible).j3-compare-chart-tooltip-desktop.png: mouse-hover tooltip with per-school values (3-school legend, colour + label).