Skip to content

fix(a11y): give the marquee drift a control and name the landmarks - #25

Merged
tkodev merged 1 commit into
mainfrom
fix/a11y-pause-landmarks
Jul 16, 2026
Merged

fix(a11y): give the marquee drift a control and name the landmarks#25
tkodev merged 1 commit into
mainfrom
fix/a11y-pause-landmarks

Conversation

@tkodev

@tkodev tkodev commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Why

From an AODA Level A pass over the site (axe-core across 7 routes at desktop and mobile, plus manual keyboard testing).

2.2.2 Pause, Stop, Hide (Level A) was failing. The marquee rows autostart, run for 80s, and had no mechanism to stop them. The hover:paused they carried looks like a control but isn't one: it's mouse-only, so it reaches neither keyboard nor touch users, and hover is not a "mechanism" under the criterion. The reduced-motion work in #24 helps readers who set the OS preference; 2.2.2 asks for an in-page control available to everyone.

Landmarks were unlabelled. Every route rendered two nav elements with no accessible names, which a screen-reader user listing landmarks cannot tell apart. (This is axe's landmark-unique, tagged best-practice rather than wcag2a, which is why a Level-A-only run doesn't surface it.)

What

Change Why
atoms/marquee.tsx Client component holding paused state, driving data-[paused=true]:paused on the track, with a keyboard-operable toggle. children still arrive from the server organisms, so the cards stay server-rendered and only the atom ships JS. Control is motion-reduce:hidden: that path is already a scroller with no drift to stop
templates/header.tsx, templates/footer.tsx, organisms/global-menu.tsx, app/design/page.tsx Landmark names (Primary, Footer, Menu, and the two specimens) passed at the call site, never baked into Nav/Menu
docs/01-standards/accessibility.md Records both as rules 4 and 5

The control lives in the atom rather than being plumbed through both consuming organisms, so every marquee is correct by construction.

Verification

Driven in Chromium via Playwright against the home testimonials:

  • Two controls found, both keyboard-reachable; focus lands on "Pause scrolling".
  • Enter gives animationPlayState: paused and the transform stops changing. Measured with the pointer parked at (5,5) so hover:paused cannot account for the result.
  • Label swaps to "Resume scrolling"; Enter again returns it to running, transform moving.
  • axe re-run with wcag2a + best-practice: landmark-unique clear.

pnpm lint:check and pnpm type:check clean.

Not covered

  • The h1s. The same pass found /, /works, /experience, /about and the 404 have no h1 (page-has-heading-one, and accessibility.md rule 1). Being handled separately.
  • 2.2.2 elsewhere. The four autoplay video loops on /works and the contour canvas are the same criterion and still have no control. A single global motion toggle would cover all three; this PR only fixes the marquee.
  • Control placement is a taste call. It sits at each row's top-right and overlaps the testimonial copy. Alternatives: move it into the Board header (one control per section, needs the organisms to go client), or give it a solid backdrop.

An AODA Level A pass found the marquee failing 2.2.2 Pause, Stop, Hide: the
rows autostart, run 80s, and offered no way to stop them. The hover:paused
they carried is mouse-only, so it reached neither keyboard nor touch, and
hover is not a mechanism under the criterion. The reduced-motion path added
earlier only serves readers who set the preference; 2.2.2 asks for a control
for everyone.

Marquee becomes a client component holding the paused state and driving
data-[paused=true] on the track. Its children still arrive from the server
organisms, so the cards stay server-rendered and only the atom ships JS. The
control is hidden under reduced motion, where the row is already a scroller
with no drift to stop.

Landmark names come from the call sites rather than the component: every route
carried two unlabelled navs, which a reader listing them cannot tell apart.
Content baked into a component would break the reusability rule.

The missing h1s the same pass found are not addressed here.
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tkodev-web-v5 Ready Ready Preview, Comment Jul 16, 2026 7:01pm

@tkodev
tkodev merged commit 9333437 into main Jul 16, 2026
3 checks passed
@tkodev
tkodev deleted the fix/a11y-pause-landmarks branch July 16, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant