Skip to content

feat(blog): 모달 오버레이 배경 격리 — portal + inert (#106)#127

Merged
Malloc72P merged 1 commit into
mainfrom
claude/issue-106-modal-a11y
Jul 1, 2026
Merged

feat(blog): 모달 오버레이 배경 격리 — portal + inert (#106)#127
Malloc72P merged 1 commit into
mainfrom
claude/issue-106-modal-a11y

Conversation

@Malloc72P

Copy link
Copy Markdown
Owner

#87에서 물리적 Tab 트랩까지는 처리했지만, 완전한 모달 배경 격리(스크린리더 가상 커서/스와이프가 배경으로 새는 것 차단)는 범위 밖으로 남겨졌던 것을 마무리합니다.

구현

  • hooks/use-modal-a11y.ts(신설): 사이드바·검색 모달이 공유하는 배경 격리 훅
    • body 스크롤 잠금(useBodyScrollLock 재사용) + 배경 콘텐츠 inert 적용/복구 일원화
    • 여러 모달이 동시에 열릴 수 있는 경우(예: 사이드바가 열린 채 Cmd+K로 검색을 열 때)에도 안전하도록 참조 카운트로 추적 — 먼저 열린 모달이 표시해 둔 inert를 나중에 닫히는 모달이 실수로 지우지 않음
    • createPortaldocument.body에 렌더하기 위한 mounted 가드 제공(SSR엔 document가 없음)
  • mobile-sidebar.tsx / search-modal.tsx: 패널을 document.body에 직접 포털 렌더 + useModalA11y로 전환, aria-modal="true" (재)부여 — 이제 실제로 배경이 격리되어 있으니 정확한 선언

테스트에서 발견한 실제 버그

search-modal.test.tsx 작성 중, 모달이 isOpen=true로 첫 렌더될 때 inert가 적용 안 되는 버그를 발견했습니다. mounted(포털 렌더 가능 시점)가 falsetrue로 바뀌는 커밋에서 dialogRef.current가 처음 채워지는데, effect 의존성이 [open]뿐이라 open 값 자체가 안 바뀌면 재실행되지 않았습니다. mounted를 의존성에 추가해 수정했습니다.

검증

  • 단위 22스위트/86개(신규: search-modal.test.tsx, 사이드바/검색모달 배경 inert 케이스)
  • 기존 테스트: 포털 렌더로 container 스코프 밖으로 나간 쿼리를 document 기준으로 수정
  • 실제 브라우저(프로덕션 빌드) 스크린샷 + 스크립트로 배경 27개 요소(script 태그 포함)가 정상 inert 처리됨을 확인, 시각적 회귀 없음
  • E2E 8개(로컬 + 프로덕션) / lint 0 / tsc clean

🤖 Generated with Claude Code

- hooks/use-modal-a11y.ts 신설: 사이드바·검색 모달이 공유하는 배경 격리 훅
  - body 스크롤 잠금(useBodyScrollLock 재사용) + 배경 콘텐츠 inert 적용/복구를 일원화
  - 여러 모달 동시 오픈(예: 사이드바 연 채 Cmd+K)에도 안전하도록 참조 카운트로 추적
  - createPortal용 mounted 가드 제공(SSR엔 document 없음)
- mobile-sidebar.tsx / search-modal.tsx: 패널을 document.body에 직접 포털 렌더 +
  useModalA11y로 전환, aria-modal="true" (재)부여 — 이제 배경이 실제로 격리되어
  스크린리더 가상 커서/스와이프가 배경으로 새지 않음
- 기존 테스트: 포털 렌더로 container 스코프 밖으로 나간 부분을 document 쿼리로 수정
- 신규 테스트: 배경 inert 적용/해제(사이드바+검색 모달), search-modal 컴포넌트 테스트 신설
  (테스트 작성 중 mounted 커밋과 open 변경이 겹치는 경우 inert 적용이 누락되는
   실제 버그를 발견해 함께 수정 — effect 의존성에 mounted 추가)
- 실제 브라우저(프로덕션 빌드)에서 배경 27개 요소(script 포함)가 정상적으로 inert 처리됨을 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
blog Ignored Ignored Jul 1, 2026 1:12am

@Malloc72P

Copy link
Copy Markdown
Owner Author

🔍 코드 리뷰 (self-review)

검증한 것 — 동시 모달(사이드바 + 검색) 시나리오

설계 시 고려했던 엣지 케이스(사이드바가 열린 채 Cmd+K로 검색을 여는 경우)를 실제 브라우저에서 확인했습니다:

  1. 사이드바 연 뒤 Cmd+K → 검색 모달 열림 + 사이드바 자신도 inert 처리됨(검색이 최상단이므로 올바름)
  2. 검색만 Esc로 닫음 → 사이드바의 inert는 해제(다시 상호작용 가능) but 배경(페이지 본문)은 여전히 inert 유지(사이드바가 아직 열려 있으므로)

참조 카운트 설계가 실전에서도 정확히 동작함을 확인했습니다(로직상 추론에서 그치지 않고 실측).

테스트가 실제 버그를 잡음

search-modal.test.tsx를 작성하다가 mounted가 늦게 true가 되는 첫 커밋에서 inert 적용이 누락되는 버그를 발견해 함께 고쳤습니다(PR 본문 참고). 커밋 전에 잡을 수 있어서 다행입니다.

검토했지만 변경하지 않은 것

  • <script> 태그도 inert 대상에 포함(SpeedInsights/Analytics 등) — 이미 실행된 스크립트의 동작에는 영향 없음(inert는 렌더링/상호작용에만 영향), 실제 프로덕션 빌드 확인에서도 정상.
  • Tab 트랩 스캔 범위를 오버레이(외곽) 요소로 통합 — 오버레이 자체엔 포커스 가능한 요소가 없어 내부 다이얼로그로 좁히나 외곽으로 두나 결과가 동일. 리팩터 단순성을 위해 외곽으로 통일.

수정할 미흡사항은 없습니다.

@Malloc72P Malloc72P merged commit 3ed450f into main Jul 1, 2026
3 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