Conversation
Tokens (single CSS edit clears most contrast failures)
- Remap --color-text-muted from text-tertiary (#9CA3AF, 2.54:1) to
gray-600 (#4B5563, 8.59:1). Eyebrow, stat labels, column headers,
intervals, pending notes, etc. now all clear 4.5:1 at the small sizes
we render them.
- Introduce --color-warning-text (#d9480f), --color-danger-text
(#b91c1c), --color-success-text (primary-700). Badge variants now use
text-warning-text / text-danger-text / text-success-text /
text-primary-strong on -soft fills, all >=4.5:1.
- Active country pill, sensitivity pill, and active nav switch from
bg-primary text-void / text-primary (~3.3-3.5:1) to bg-primary-strong
text-white / text-primary-strong (>=7:1).
Document landmarks
- Each page now has an <h1> (sr-only on / and /paper so the existing
visual hierarchy survives).
- <main id="main"> on both pages plus a "Skip to main content" link in
layout.tsx (visually hidden until focused).
- Per-route metadata so /paper has a distinct browser tab title.
Focus and keyboard
- Global :focus-visible ring on every interactive element via
globals.css, scoped to focus-visible so mouse clicks don't show it.
- Compact ViewSelector pills bumped to 24x24+ (WCAG 2.2 SC 2.5.8).
- Disabled sensitivity buttons use aria-disabled + click guard instead
of the HTML disabled attr, so screen readers can read the title /
context explaining why ("no UK rows under this slice; ..."). Active
state strips aria-pressed when disabled.
- Hidden header chrome stays out of tab order (already correct);
re-enables once visible.
Live regions and table semantics
- Leaderboard wrapped in role="region" aria-live="polite" with a
status label that announces model count and active view. The grid
uses role="row" / role="columnheader" / row-level aria-label so SR
users hear "Rank N, model name, score X%".
- Bootstrap interval text now carries aria-label
("Rank 1 across bootstrap draws; 95% score interval ...") so it
reads as a sentence, not opaque numbers.
- Open-set caveat is an <aside aria-labelledby> rather than the
non-standard role="note".
Tooltip
- ExplanationTooltip now Escape-dismisses (returns focus to the
trigger), drops aria-label that was double-naming the button, and
removes pointer-events-none so a mouse user can hover into the
tooltip to read long explanations. Keeps aria-expanded on the
trigger.
Iframe (/paper)
- sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
loading="lazy" referrerPolicy="same-origin"; "Back to top" anchor
outside the iframe so keyboard users can escape.
Reduced motion
- @media (prefers-reduced-motion: reduce) snaps all transitions and
animations to instant. SiteHeader's scroll-progress hook detects
the same and snaps to either expanded or collapsed instead of
running RAF lerping.
Smaller fixes
- ProviderMark: role="img" + single aria-label (no double-name);
uses currentColor so it stays legible on any surface.
- "by PolicyEngine" pill: dropped redundant aria-label/title that
caused name-in-label mismatch with visible "by PolicyEngine".
- ScenarioExplorer <select> now has htmlFor/id linking to its label.
- <details> summaries get a rotating chevron; without one, screen
readers and users with limited contrast had no disclosure cue.
- Heatmap legend gets an sr-only caption explaining color is a
redundant cue and the printed % is the source of truth.
- Shuffle button single-named (dropped redundant title + sr-only span).
Verification
- bun run lint clean.
- bun run build clean (still ○ static).
- SSR HTML / contains: <h1 class="sr-only">, skip-to-content link,
role=region/row/columnheader, aria-pressed="true" on the active
country and sensitivity pills, aria-disabled on Binary-on-Global,
aria-current on the in-section nav link.
- SSR HTML /paper contains: <h1 class="sr-only">, skip link, iframe
with sandbox + loading="lazy".
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements every Blocker, Major, and Minor finding from the in-tree accessibility review. Single PR; nothing functional changes for sighted mouse users.
What changes
Tokens (one CSS edit clears most contrast failures)
--color-text-mutedremapped from text-tertiary (#9CA3AF, 2.54:1) to gray-600 (#4B5563, 8.59:1). Eyebrow, stat labels, column headers, intervals, pending notes — all clear 4.5:1 at 10–11px.--color-warning-text/--color-danger-text/--color-success-textfor use on-softfills.Badgevariants now clear 4.5:1.bg-primary text-void/text-primary(~3.3–3.5:1) tobg-primary-strong text-white/text-primary-strong(≥7:1).Document landmarks
<h1>per page (sr-only) +<main id="main">+ skip-to-content link inlayout.tsx.<title>(Paper — PolicyBench).Focus + keyboard
:focus-visiblering (nooutline:noneregressions).aria-disabled+ click guard, retaining tab-discovery and thetitlecontext.Live regions + table semantics
role="region"+aria-live="polite"+ status announcing model count and active view; rows / column headers labeled via ARIA roles; per-rowaria-label="Rank N, model, score X%".aria-label="Rank 1 across bootstrap draws; 95% score interval 79.8% to 83.5%".role="note"(no such role) to<aside aria-labelledby>.Tooltip
ExplanationTooltipEscape-dismisses (returns focus), drops double-naming, removespointer-events-noneso the user can move the mouse into a long tooltip to read it, exposesaria-expandedon the trigger.Iframe
/paperiframe:sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox",loading="lazy",referrerPolicy="same-origin". Adds a "Back to top" anchor outside the iframe so keyboard users can escape.Reduced motion
@media (prefers-reduced-motion: reduce)snaps all animations/transitions to instant.SiteHeaderscroll-progress hook detects the same and skips the RAF lerp.Other
ProviderMark:role="img"+ singlearia-label; usescurrentColor.aria-label/titlethat caused name-in-label mismatch with the visible "by PolicyEngine" text.ScenarioExplorer<select>now hashtmlFor/idassociation.<details>summaries get a rotating chevron.Verification
bun run lintclean.bun run buildclean (still○static)./contains<h1 class="sr-only">,skip-to-content,role="region",aria-live="polite", 4role="columnheader", 13role="row",aria-pressed="true"on active pills,aria-disabledon Binary-on-Global,aria-currenton active nav./papercontains<h1 class="sr-only">, skip link, iframe withsandbox+loading="lazy".Out of scope
<table>element (the existing CSS-grid + ARIA-row pattern works without a media-query swap and ships in this PR via roles). Real<table>is a larger refactor and not required for AA.🤖 Generated with Claude Code