Skip to content

♿️ fix: 히트맵 날짜 셀을 키보드 포커스에서도 읽히게 수정 - #13

Open
za0012 wants to merge 1 commit into
Nextsfrom
a11y/heatmap-keyboard-focus
Open

za0012 wants to merge 1 commit into
Nextsfrom
a11y/heatmap-keyboard-focus

Conversation

@za0012

@za0012 za0012 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

히트맵 날짜 셀의 툴팁이 마우스로만 열렸습니다. Tab으로 셀에 들어가면 포커스 링만 뜨고 풀이 수는 끝내 표시되지 않았습니다.

왜 이렇게 됐나

셀을 <circle> 대신 투명 <button>으로 감싼 이유가 코드 주석에 남아 있습니다.

//버튼으로 한 이유... 우선 아래에 있는 onMouse이벤트를 circle같은 정적인 svg도형에 넣으면 접근성 규칙에 걸린다.
// biome측에서 button에 넣는 것이 좋다고 하여 button안에 넣게 되었다.

그 린트 규칙이 가리키던 문제가 바로 이겁니다. 규칙이 요구하는 건 태그 교체가 아니라 *"마우스로 되는 일은 키보드로도 될 것"*입니다.

규칙이 요구하는 것 수정 전
상호작용 요소를 의미 있는 태그로 (<button>)
그 상호작용이 키보드로도 동작

요소 의미만 맞추고 상호작용은 마우스 전용으로 남아 있었습니다. mouseenter/mouseleave는 포인터 장치에서만 발생하고, 키보드 이동은 focus/blur를 발생시킵니다. 브라우저에 Tab을 mouseenter로 바꿔주는 동작은 없습니다.

무엇을 고쳤나

  const cx = weekIndex * STEP + STEP / 2;
  const cy = dayOfWeek * STEP + STEP / 2;

+ // 마우스와 키보드가 같은 동작을 하도록 핸들러를 하나로 두고 양쪽에 건다.
+ const showTooltip = () => setHovered({ date, value, cx, cy });
+ const hideTooltip = () => setHovered(null);
+
  return (
    <button
      ...
-     onMouseEnter={() => setHovered({ date, value, cx, cy })}
-     onMouseLeave={() => setHovered(null)}
+     onMouseEnter={showTooltip}
+     onMouseLeave={hideTooltip}
+     onFocus={showTooltip}
+     onBlur={hideTooltip}

hovered 상태가 이미 { date, value, cx, cy }를 들고 있고 툴팁 위치 계산도 cx/cy로 동작하고 있어, 새 상태나 새 컴포넌트가 필요 없었습니다. 마우스와 키보드가 문자 그대로 같은 핸들러를 씁니다.

누가 막혀 있었나

aria-label이 붙어 있어 스크린리더 사용자는 원래 값을 읽을 수 있었습니다. 막혀 있던 건 다른 쪽입니다.

  • 눈은 보이는데 마우스를 쓰지 않는 사용자 — 손목터널·수전증·일시적 부상, 트랙패드 없는 환경. 포커스 링은 보이는데 숫자가 안 떴습니다.
  • 화면 확대 사용자 — 대부분 키보드로 이동합니다.

검증 (개발 서버, /mypage)

focusin  → 툴팁 "2026-03-20: 0문제" 표시   (해당 셀의 aria-label과 일치)
focusout → 툴팁 사라짐
마우스 경로 → 기존과 동일하게 동작

검증 방법에 함정이 하나 있었습니다. 브라우저 창이 OS 포커스를 갖지 않으면 element.focus()activeElement만 바꾸고 focus 이벤트를 발생시키지 않습니다. 처음엔 이걸 모르고 .focus()로 시험해서 수정 후에도 툴팁이 안 뜨는 것처럼 보였습니다. document.hasFocus() === false를 확인하고, React가 실제로 구독하는 focusin/focusout으로 다시 측정했습니다.

tsc --noEmit   0
bun test       12 pass
bun run build  통과
biome          0 error

검토했지만 넣지 않은 것

:focus-visible 가드(마우스 클릭 포커스를 걸러내기)를 고려했지만 넣지 않았습니다. 클릭 후 마우스를 떼면 onMouseLeave가 먼저 발생해 hovered를 비우므로, 이 위젯에서는 관측 가능한 차이가 없습니다. 근거 없는 줄을 남기지 않았습니다.

남은 한계

365개가 전부 탭 순서에 있습니다. 히트맵을 지나 다음 요소로 가려면 Tab을 365번 눌러야 합니다.

이 PR은 *"마우스 없이 각 날짜의 값을 확인할 수 있다"*를 성립시키는 데까지입니다. 위젯을 편하게 만드는 건 roving tabindex(셀 하나만 tabIndex=0, 화살표로 이동)이고, 별도로 다룹니다.

🤖 Generated with Claude Code

날짜 셀의 툴팁이 onMouseEnter/onMouseLeave 로만 열리고 닫혔다.
mouseenter/mouseleave 는 포인터 장치에서만 발생하고 키보드 이동은
focus/blur 를 발생시키므로, Tab 으로 셀에 들어가면 포커스 링만 뜨고
풀이 수는 끝내 표시되지 않았다.

셀을 <circle> 대신 <button> 으로 감싼 것은 원래 이 문제를 가리킨
린트 규칙 때문이었다(주석에 남아 있다). 그 규칙이 요구하는 건
태그 교체가 아니라 "마우스로 되는 일은 키보드로도 될 것"인데,
지금까지는 요소 의미만 맞추고 상호작용은 마우스 전용으로 남아 있었다.

핸들러를 showTooltip/hideTooltip 으로 한 번만 정의하고
onMouseEnter/onMouseLeave 와 onFocus/onBlur 양쪽에 같은 것을 걸었다.
hovered 상태가 이미 { date, value, cx, cy } 를 들고 있어 새 상태나
위치 계산은 필요 없었다.

aria-label 이 있어 스크린리더는 원래 값을 읽을 수 있었다.
막혀 있던 건 눈은 보이지만 마우스를 쓰지 않는 사용자와
화면 확대 사용자다.

검증(개발 서버, /mypage):
  focusin  → 툴팁 "2026-03-20: 0문제" 표시
  focusout → 툴팁 사라짐
  마우스 경로는 기존과 동일

  * 브라우저 창이 OS 포커스를 갖지 않으면 element.focus() 가
    activeElement 만 바꾸고 focus 이벤트를 발생시키지 않는다.
    그래서 React 가 실제로 구독하는 focusin/focusout 으로 확인했다.

tsc --noEmit 0 / bun test 12 pass / build 통과

남은 한계: 셀 365개가 모두 탭 순서에 들어 있어 히트맵을 지나가려면
Tab 을 365번 눌러야 한다. roving tabindex 는 별도로 다룬다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@za0012
za0012 deployed to ai-review August 11, 2026 04:43 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

자동 PR 코드 리뷰

  • 실행 유형: PR 리뷰
  • 생성 시각: 2026-08-11T04:43:56.711Z
  • 기준 커밋: 637a1035ed12e43cfa4b2dd79a8468e05a4ec65e
  • 대상 커밋: e8a7aa992b384b252c1aa55a4a281766636ef649
  • 변경 파일 수: 1
  • 발견 항목 수: 0
  • 이번 PR 영향 가능 항목: 0
  • 기존 실패 추정 항목: 0

검증 결과

이번 PR 영향 가능 항목

이번 PR 변경 파일에서 직접 발생한 것으로 보이는 항목은 없습니다.

기존 실패로 추정되는 항목

기존 실패로 분류된 항목은 없습니다.

경고

  • OpenAI review skipped because OPENAI_API_KEY is missing.

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