Skip to content

fix: semantic card & accessibility cleanup v1 - #20

Merged
UAJOP merged 3 commits into
mainfrom
fix/accessibility-semantic-cards-v1
Aug 22, 2026
Merged

fix: semantic card & accessibility cleanup v1#20
UAJOP merged 3 commits into
mainfrom
fix/accessibility-semantic-cards-v1

Conversation

@UAJOP

@UAJOP UAJOP commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Removes invalid interactive-card semantics, fixes accessible names that were being silently dropped, and adds regression guards — while keeping the visual design and mouse UX identical.

The problem

14 cards (10 works.html, 4 games.html) were <article role="link" tabindex="0"> containing their own links. That is invalid ARIA (interactive content inside a link role), created a redundant tab stop per card, and made Enter/Space ambiguous against the nested anchors.

Separately, 50 containers used <div aria-label="...">. A plain <div> maps to the generic role, which prohibits an accessible name — so every one of those labels was silently discarded by browsers and AT.

Changes

Cards → native semantics. Every card already contained a real anchor to its own primary destination, so the card title became that anchor and the <article> went back to being a plain container. The aria-label="Open X" is dropped now that the heading is the link — and a title like "SINAMA — AI Agent Reliability Lab" is far better link text than the generic "View Details" it sat next to.

Whole-card mouse navigation preserved, but now mouse-only and better behaved: a shared shouldIgnoreCardActivation() guard skips nested interactive elements, and no longer hijacks modifier clicks, middle clicks or text selection. The simulated Enter/Space handlers were removed along with the tabindex they existed to serve.

Labelled containers now carry role="group", which supports naming — one attribute, no structural or CSS change, consistent with the role="group" already used by the V2 recruiter role switch and Evidence Explorer toolbar.

CSS keeps the design pixel-identical: the new title anchor would otherwise inherit .project-content action-link styling (brand colour, inline-flex, margin-top: 18px), so it explicitly inherits the heading design instead. The pointer affordance moved from [role="link"] to the data attributes the script actually reads.

Results

before after
HTML validation 0 errors, 67 warnings 0 errors, 3 warnings
prefer-native-element 14 0
aria-label-misuse 53 3
nested interactive elements 14 cards 0
cards with role/tabindex 14 0

The 3 remaining warnings are <canvas aria-label> — reported only as "not recommended", and the correct way to name a canvas. Removing them would reduce accessibility, so they are kept deliberately.

Tab-stop count per card is unchanged (the article stop became the title stop); the improvement is that every stop is now a valid, uniquely-named native link instead of a simulated one wrapping other links.

Verification

qa:portfolio pass · qa:html 0 errors / 3 warnings · qa:spelling 0 · pa11y-ci 11/11 · JS syntax 14/14 · links 0 broken (453 refs; all 14 card title anchors resolve) · Lighthouse a11y/best-practices/SEO 100, CLS ~0 (performance unchanged at the pre-existing CDN-bound 63-66).

Browser-verified: whole-card click navigates; nested link wins over the card (proved on the AI Flow Puzzle card, whose "Play" target differs from the card target); modifier/middle clicks and text selection no longer trigger navigation; external links keep target="_blank" + rel="noopener"; filters, search, Recruiter Mode, role deep links, Command Palette, Ajoop, mobile nav, language and theme all unaffected; 19/19 pages load with no JS errors; 45 viewport×page checks clean at 1440/1366/768/390/360/844×390.

8 new regression guards, each verified to fail when its regression is reintroduced, then restored.

No merge. Draft for review.

🤖 Generated with Claude Code

UAJOP and others added 3 commits August 22, 2026 15:21
The 10 works.html and 4 games.html cards used role="link" + tabindex="0"
on an <article> that already contained real action links. That is an
invalid ARIA pattern (nested interactive content inside a link role),
produced a redundant tab stop per card, and made Enter/Space ambiguous.

Each card title is now a real anchor to the card's primary destination,
and the article is a plain container again. The aria-label that
duplicated the heading is dropped now that the heading itself is the
link. Titles are far better link text than the generic "View Details".

Whole-card mouse navigation is preserved but is now mouse-only, and the
guard no longer hijacks modifier clicks, middle clicks or text selection.
The fake Enter/Space handlers are removed with the tabindex they served.

CSS keeps the heading design identical (the new anchor would otherwise
inherit .project-content action-link styling) and moves the pointer
affordance from [role="link"] to the data attributes the script reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
50 containers used <div aria-label="...">. A plain <div> maps to the
generic role, which prohibits an accessible name, so browsers and
assistive technology silently dropped every one of those labels.

Each of these is a labelled set of related controls, links or items, so
they now carry role="group", which supports naming. This is the smallest
correct fix: one attribute, no structural or CSS change, and consistent
with the role="group" already used by the V2 recruiter role switch and
Evidence Explorer toolbar.

The three remaining aria-label warnings are on <canvas> elements, where
aria-label is the correct way to provide an accessible name. They are
reported only as "not recommended" and are kept deliberately.

HTML validation: 67 warnings -> 3 (0 errors throughout).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Locks in the semantics this branch fixed so they cannot silently regress:

- project cards must not simulate a link (role="link") or carry tabindex
- a card with a whole-card navigation target must expose that exact
  destination as a real title anchor
- anchors must never nest
- a labelled <div> must carry a role that supports an accessible name
- card navigation must stay mouse-only: setupProjectCardNavigation and
  setupGameCards must route through shouldIgnoreCardActivation and must
  not re-add simulated keydown activation

Each guard was verified to fail when its regression is reintroduced,
then restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@UAJOP
UAJOP marked this pull request as ready for review August 22, 2026 13:04
@UAJOP
UAJOP merged commit 99ca2c9 into main Aug 22, 2026
2 checks passed
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