[PR #1820] [MERGED] fix(staffml): drop misleading role=button from MetaTooltip and stop nesting it inside a real button #34610

Closed
opened 2026-07-14 19:22:23 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/harvard-edge/cs249r_book/pull/1820
Author: @farhan523
Created: 5/29/2026
Status: Merged
Merged: 6/2/2026
Merged by: @profvjreddi

Base: devHead: fix/metatooltip-role-button-and-nested


📝 Commits (1)

  • 4b56e5b fix(staffml): drop misleading role=button from MetaTooltip and stop nesting it inside a real button

📊 Changes

3 files changed (+77 additions, -4 deletions)

View changed files

interviews/staffml/src/__tests__/meta-tooltip.test.tsx (+67 -0)
📝 interviews/staffml/src/components/MetaTooltip.tsx (+9 -3)
📝 interviews/staffml/src/components/vault/TopicDetail.tsx (+1 -1)

📄 Description

Summary

Two related a11y issues in MetaTooltip / LevelBadge:

  1. role=\"button\" with no handler. MetaTooltip's trigger declared role=\"button\" with no onClick / onKeyDown — screen readers announced "button" but Enter/Space did nothing.
  2. Nested interactive elements. LevelBadge wraps its badge in MetaTooltip by default, and vault/TopicDetail renders LevelBadge inside a real <button> for the drill-by-difficulty rows. The result was <button><span role=\"button\" tabIndex={0}>…</span></button> — invalid HTML and a double tab stop for one logical control.

Changes

  • MetaTooltip.tsx: drop role=\"button\". Keep tabIndex={0} so keyboard users can still focus the trigger and the tooltip appears via group-focus-within. Keep aria-describedby linking to the role=\"tooltip\" body.
  • vault/TopicDetail.tsx: pass withTooltip={false} on the nested LevelBadge (the withTooltip prop already exists on LevelBadge for exactly this case).
  • New regression test asserts no role=\"button\", trigger stays focusable, aria-describedby still resolves to a role=\"tooltip\" body, and LevelBadge with withTooltip={false} skips the wrapper.

Test plan

  • npx vitest run → 42/42 passing (was 37/37 on dev — the 5 new tests cover the above)
  • npx tsc --noEmit → clean
  • Manual a11y: tab through the drill-by-difficulty list at /?topic=... — each row should be one tab stop (the parent <button>), not two
  • Manual a11y: hover a LevelBadge outside any button (e.g. on the practice page header) — tooltip still appears; screen reader announces the tooltip text via aria-describedby instead of "button"

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/harvard-edge/cs249r_book/pull/1820 **Author:** [@farhan523](https://github.com/farhan523) **Created:** 5/29/2026 **Status:** ✅ Merged **Merged:** 6/2/2026 **Merged by:** [@profvjreddi](https://github.com/profvjreddi) **Base:** `dev` ← **Head:** `fix/metatooltip-role-button-and-nested` --- ### 📝 Commits (1) - [`4b56e5b`](https://github.com/harvard-edge/cs249r_book/commit/4b56e5b9cfcefca167b3660202f1d2f8da432e1f) fix(staffml): drop misleading role=button from MetaTooltip and stop nesting it inside a real button ### 📊 Changes **3 files changed** (+77 additions, -4 deletions) <details> <summary>View changed files</summary> ➕ `interviews/staffml/src/__tests__/meta-tooltip.test.tsx` (+67 -0) 📝 `interviews/staffml/src/components/MetaTooltip.tsx` (+9 -3) 📝 `interviews/staffml/src/components/vault/TopicDetail.tsx` (+1 -1) </details> ### 📄 Description ## Summary Two related a11y issues in `MetaTooltip` / `LevelBadge`: 1. **`role=\"button\"` with no handler.** `MetaTooltip`'s trigger declared `role=\"button\"` with no `onClick` / `onKeyDown` — screen readers announced \"button\" but Enter/Space did nothing. 2. **Nested interactive elements.** `LevelBadge` wraps its badge in `MetaTooltip` by default, and `vault/TopicDetail` renders `LevelBadge` inside a real `<button>` for the drill-by-difficulty rows. The result was `<button><span role=\"button\" tabIndex={0}>…</span></button>` — invalid HTML and a double tab stop for one logical control. ### Changes - `MetaTooltip.tsx`: drop `role=\"button\"`. Keep `tabIndex={0}` so keyboard users can still focus the trigger and the tooltip appears via `group-focus-within`. Keep `aria-describedby` linking to the `role=\"tooltip\"` body. - `vault/TopicDetail.tsx`: pass `withTooltip={false}` on the nested `LevelBadge` (the `withTooltip` prop already exists on `LevelBadge` for exactly this case). - New regression test asserts no `role=\"button\"`, trigger stays focusable, `aria-describedby` still resolves to a `role=\"tooltip\"` body, and `LevelBadge` with `withTooltip={false}` skips the wrapper. ## Test plan - [x] `npx vitest run` → 42/42 passing (was 37/37 on dev — the 5 new tests cover the above) - [x] `npx tsc --noEmit` → clean - [ ] Manual a11y: tab through the drill-by-difficulty list at `/?topic=...` — each row should be one tab stop (the parent `<button>`), not two - [ ] Manual a11y: hover a `LevelBadge` outside any button (e.g. on the practice page header) — tooltip still appears; screen reader announces the tooltip text via `aria-describedby` instead of \"button\" --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-07-14 19:22:23 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/cs249r_book#34610