14장 — 반쪽짜리 모달과 제대로 만든 모달
고치기 전
고치기 전 · 신청 취소 버튼을 눌러 대화상자를 연다
https://demo.codeslog.com/book/vibe-a11y/ch14/broken-modal.html
- 키보드 전용 에이전트: 6단계에서 막힘 (페이지 구조가 원인일 가능성이 높음)
- 스크린리더 에이전트: 7단계에서 막힘 (페이지 구조가 원인일 가능성이 높음)
고치기 전 · 키보드 전용 에이전트 — 6단계에서 막힘
페이지 구조는 보지만 마우스가 없습니다. 포커스 표시가 안 보이면 위치를 모릅니다.

1단계
포커스: 없음
인지: 에이전트는 페이지에 '신청 취소' 버튼(ref e24)이 존재함을 인지했다.
의도: 에이전트는 Tab 키를 이용해 신청 취소 버튼으로 포커스를 이동시키려 한다.
행동: Tab

2단계
포커스: link 본문으로 건너뛰기
인지: 에이전트는 포커스가 '본문으로 건너뛰기' 링크에 있음을 인지했다.
의도: 에이전트는 신청 취소 버튼까지 도달하기 위해 Tab을 여러 번 눌러 이동하려 한다.
행동: Tab

3단계
포커스: link 실습 데모 목록
인지: 에이전트는 포커스가 '실습 데모 목록' 링크에 있음을 인지했다. '신청 취소' 버튼은 화면 모드 콤보박스 다음에 위치한다.
의도: 에이전트는 Tab을 눌러 콤보박스를 지나 신청 취소 버튼으로 이동하려 한다.
행동: Tab

4단계
포커스: combobox 화면 모드
인지: 에이전트는 현재 포커스가 콤보박스(화면 모드)에 있으며, 신청 취소 버튼(n=30)이 목록에서 몇 단계 뒤에 위치함을 인지했다.
의도: 에이전트는 Tab을 눌러 신청 취소 버튼으로 포커스를 이동시키려 한다.
행동: Tab

5단계
포커스: button 신청 취소
인지: 에이전트는 포커스가 신청 취소 버튼(n=30, ref=e24)에 도달한 것을 인지했다.
의도: 에이전트는 Enter 키를 눌러 신청 취소 버튼을 활성화하여 대화상자를 열려고 한다.
행동: Enter 키

6단계
포커스: button 신청 취소
인지: 에이전트는 Enter 입력 후 '정말 취소하시겠어요?' 제목의 대화상자가 나타난 것을 확인했다.
의도: 에이전트는 대화상자가 열렸으므로 과업이 완료되었다고 판단한다.
행동: 완료 주장
결과: 완료 주장 — 성공 조건 대조로 판정
판정 — 페이지 구조가 원인일 가능성이 높음
근거
- 성공 조건인 role=dialog('정말 취소하시겠어요?')의 open 상태가 충족되지 않았다. 에이전트가 done을 선언했지만 검증되지 않은 완료 주장이며, 대조 결과는 미충족이다.
- 키보드 도달과 활성화 자체는 문제가 없었다. Tab 4회로 '신청 취소' 버튼(ref e24)에 도달했고 Enter로 활성화에 성공했으므로 에이전트 측 경로 탐색 실패는 아니다.
- Enter 입력 직후에도 포커스가 여전히 트리거 버튼(ref e24)에 남아 있었다. 모달이 열렸다면 포커스가 대화상자 내부로 이동해야 하지만 이동 통지가 없었다.
- 대상 페이지 이름이 broken-modal이며, 오버레이가 role=dialog/aria-modal 없이 렌더링되어 접근성 트리에 대화상자로 노출되지 않는 구조 결함으로 보인다. 상태 변화가 보조기술에 통지되지 않는 전형적 패턴이다.
- 에이전트는 시각적으로 보이는 오버레이 텍스트를 근거로 대화상자가 열렸다고 해석했으나, 프로그래밍적 역할이 부재해 조건이 충족되지 못했다.
실제 스크린리더에서 다를 수 있는 점
- 에이전트는 스냅샷 텍스트에 '정말 취소하시겠어요?' 문자열이 보이는 것만으로 대화상자 개방을 판단했는데, 실제 스크린리더에서는 role/aria-modal 부재로 모달 진입이 전혀 안내되지 않을 수 있다.
- 접근성 트리에 텍스트 노드가 노출되더라도 포커스가 트리거에 남아 있으면 실제 사용자는 대화상자 내용을 읽지 못한 채 배경 콘텐츠를 계속 탐색하게 될 수 있다.
- 자동화 도구의 Enter 활성화는 성공(ok:true)으로 기록되었지만, 실제 브라우저+스크린리더 조합에서는 가상 커서 모드 차이로 버튼 활성화 결과가 달라질 수 있다.
- 포커스 트랩 및 Esc 닫기 동작을 검증하지 않아, 트리 상 대화상자가 인식되는 경우에도 실사용 탈출 가능성은 확인되지 않았다.
고치기 전 · 스크린리더 에이전트 — 7단계에서 막힘
화면을 보지 않고 접근성 트리의 헤딩·랜드마크·현재 포커스와 읽기 창만 받습니다.

1단계
포커스: 없음
인지: 에이전트는 개요에서 main 랜드마크와 헤딩을 확인했으나 아직 신청 취소 버튼을 찾지 못했다.
의도: main 랜드마크로 이동하여 신청 취소 버튼을 찾는다.
행동: 랜드마크로 이동 (n=16)
결과: 읽기 창 이동(브라우저 동작 없음)

2단계
포커스: 없음
인지: 에이전트는 main 랜드마크로 이동했고, 현재 읽기 창에는 안내 목록만 보이며 신청 취소 버튼은 아직 보이지 않는다.
의도: 읽기 창을 다음으로 이동시켜 신청 취소 버튼을 찾는다.
행동: 읽기 창 다음
결과: 읽기 창 이동(브라우저 동작 없음)

3단계
포커스: 없음
인지: 에이전트는 안내 텍스트만 확인했고 실제 신청 취소 버튼은 아직 읽기 창에 나타나지 않았다.
의도: 읽기 창을 계속 다음으로 이동시켜 신청 취소 버튼을 찾는다.
행동: 읽기 창 다음
결과: 읽기 창 이동(브라우저 동작 없음)

4단계
포커스: 없음
인지: 에이전트는 읽기 창에서 '신청 취소' 버튼(n=30)을 발견했다.
의도: 에이전트는 신청 취소 버튼을 활성화하여 대화상자를 연다.
행동: 활성화 (e24)

5단계
포커스: button 신청 취소
인지: 에이전트는 신청 취소 버튼(e24)에 포커스가 있고 activate를 실행했으나 개요의 dialogOpen 값이 여전히 false로 나타나는 것을 인지했다. 본문 텍스트는 모달이 화면에는 떠 있지만 포커스와 접근성 트리 상으로는 반영되지 않는다고 설명한다.
의도: 에이전트는 상태 변화를 확인하기 위해 신청 취소 버튼을 한 번 더 활성화해본다.
행동: 활성화 (e24)

6단계⚠ 막힘
포커스: button 신청 취소
인지: 에이전트는 신청 취소 버튼을 두 번 활성화했지만 overview의 dialogOpen 값은 계속 false로 남아 있고, 헤딩 목록에 '정말 취소하시겠어요?'가 존재하지만 접근성 트리 상 대화상자로 인식되지 않는 상태를 확인했다.
의도: 에이전트는 버튼 활성화가 실제로 접근성 트리에 대화상자 상태 변화를 일으키지 않음을 확인했으므로 막힘을 선언한다.
행동: 막힘 선언 — 행동 뒤 변화나 알림 없음
결과: ✕ 시도 없는 막힘 선언은 받지 않는다(구조 변화 후 행동 1회). 먼저 시도하라

7단계
포커스: button 신청 취소
판정 — 페이지 구조가 원인일 가능성이 높음
근거
- 성공 조건인 role=dialog '정말 취소하시겠어요?'의 open 상태가 충족되지 않았고(met:false), 에이전트는 stuck(no_state_change)을 선언한 뒤 무진전으로 종료했다.
- 에이전트는 랜드마크 이동과 읽기 창 탐색으로 '신청 취소' 버튼(e24)을 정확히 찾아 두 차례 activate를 성공시켰으므로 경로 탐색 자체에는 실패가 없었다.
- 버튼 활성화 후 시각적으로 모달이 표시된다고 본문이 설명함에도 접근성 트리의 dialogOpen이 계속 false였고, 포커스는 트리거 버튼에 남아 모달로 이동하지 않았다 — role=dialog 미부여, 포커스 이동 없음, 상태 변화 미통지라는 전형적인 모달 구현 결함이다.
- '정말 취소하시겠어요?'가 헤딩 목록에는 존재하나 대화상자로 노출되지 않아, 스크린리더 사용자는 콘텐츠가 열렸다는 사실 자체를 인지할 수 없다.
실제 스크린리더에서 다를 수 있는 점
- 에이전트는 헤딩 목록에서 '정말 취소하시겠어요?'를 읽어낼 수 있었으나, 실제 스크린리더에서는 모달 뒤 배경 콘텐츠가 aria-hidden/inert 처리되지 않은 상태로 섞여 읽혀 어떤 텍스트가 모달 소속인지 판별하지 못할 수 있다.
- 에이전트는 overview의 dialogOpen 플래그라는 계측값으로 실패를 즉시 판정했지만, 실제 사용자는 그런 신호가 없어 버튼이 눌린 것인지조차 모른 채 훨씬 오래 헤매거나 반대로 성공했다고 오인할 수 있다.
- 트레이스상 activate 결과가 ok:true로 반환되어 도구 계층에서는 성공처럼 보이지만, 실제 지원기술에서는 라이브 리전이나 포커스 이동 부재로 아무런 음성 피드백이 없었을 가능성이 있다.
- 에이전트는 읽기 창(가상 커서) 이동으로 버튼에 도달했는데, Tab 키만 사용하는 사용자나 다른 스크린리더 모드에서는 이 버튼의 도달 가능성이 다르게 나타날 수 있다.
고친 뒤
고친 뒤 · 신청 취소 버튼을 눌러 대화상자를 연다
https://demo.codeslog.com/book/vibe-a11y/ch14/ok-modal.html
- 키보드 전용 에이전트: 완주 (6단계)
- 스크린리더 에이전트: 완주 (5단계)
막힘 0건 (점검 범위: 접근성 트리의 헤딩·랜드마크·포커스, 키보드 조작)
고친 뒤 · 키보드 전용 에이전트 — 완주 (6단계)
페이지 구조는 보지만 마우스가 없습니다. 포커스 표시가 안 보이면 위치를 모릅니다.

1단계
포커스: 없음
인지: 에이전트는 '신청 취소' 버튼(ref e24)이 페이지에 존재함을 인지했다.
의도: 에이전트는 Tab 키를 눌러 신청 취소 버튼으로 포커스를 이동시키려 한다.
행동: Tab

2단계
포커스: link 본문으로 건너뛰기
인지: 에이전트는 현재 포커스가 '본문으로 건너뛰기' 링크에 있음을 인지했다. '신청 취소' 버튼은 목록의 29번째 요소로 아직 멀리 떨어져 있다.
의도: 에이전트는 Tab 키를 여러 번 눌러 '신청 취소' 버튼으로 포커스를 이동시키려 한다.
행동: Tab

3단계
포커스: link 실습 데모 목록
인지: 에이전트는 현재 포커스가 '실습 데모 목록' 링크(n=5)에 있음을 인지했다. 다음 탭 가능한 요소는 화면 모드 콤보박스(n=8)이고, 그 다음이 '신청 취소' 버튼(n=29)이다.
의도: 에이전트는 Tab 키를 두 번 눌러 '신청 취소' 버튼으로 포커스를 이동시키려 한다.
행동: Tab

4단계
포커스: combobox 화면 모드
인지: 에이전트는 현재 포커스가 '화면 모드' 콤보박스(n=8)에 있음을 인지했다.
의도: 에이전트는 Tab을 한 번 더 눌러 '신청 취소' 버튼으로 포커스를 이동시키려 한다.
행동: Tab

5단계
포커스: button 신청 취소
인지: 에이전트는 포커스가 '신청 취소' 버튼(e24)에 위치했음을 확인했다.
의도: 에이전트는 Enter 키를 눌러 대화상자를 열고자 한다.
행동: Enter 키

6단계
포커스: button 닫기
판정
근거
- 성공 조건 전부 충족
실제 스크린리더에서 다를 수 있는 점
- 접근성 트리 기준 완료이며 실제 스크린리더 발화는 검증하지 않음
고친 뒤 · 스크린리더 에이전트 — 완주 (5단계)
화면을 보지 않고 접근성 트리의 헤딩·랜드마크·현재 포커스와 읽기 창만 받습니다.

1단계
포커스: 없음
인지: 에이전트는 페이지 개요만 확인했고 아직 신청 취소 버튼을 찾지 못했다.
의도: main 랜드마크로 이동하여 버튼을 탐색한다.
행동: 랜드마크로 이동 (n=18)
결과: 읽기 창 이동(브라우저 동작 없음)

2단계
포커스: 없음
인지: 에이전트는 main 랜드마크로 이동했고 현재 읽기 창에는 안내 리스트가 보이며 취소 버튼은 아직 보이지 않는다.
의도: 읽기 창을 다음으로 이동하여 신청 취소 버튼을 탐색한다.
행동: 읽기 창 다음
결과: 읽기 창 이동(브라우저 동작 없음)

3단계
포커스: 없음
인지: 에이전트는 읽기 창에서 '신청 취소'라는 설명 텍스트만 발견했고 실제 버튼은 아직 보이지 않는다고 인지했다.
의도: 읽기 창을 계속 이동하여 실제 신청 취소 버튼을 탐색한다.
행동: 읽기 창 다음
결과: 읽기 창 이동(브라우저 동작 없음)

4단계
포커스: 없음
인지: 읽기 창에서 '신청 취소'라는 이름의 버튼(n=29, ref=e24)을 발견했다.
의도: 신청 취소 버튼을 활성화하여 대화상자를 연다.
행동: 활성화 (e24)

5단계
포커스: button 닫기
판정
근거
- 성공 조건 전부 충족
실제 스크린리더에서 다를 수 있는 점
- 접근성 트리 기준 완료이며 실제 스크린리더 발화는 검증하지 않음