접근성 준수와 스크린리더
스크린리더를 신경 쓰지 않고 개발을 하는 경우, 스크린리더 사용자를 차별하는 개발을 할 수가 있다. 웹 접근성을 준수하여 장애가 있는 사용자가 보다 쉽게 차별 없이 웹 사이트나 애플리케이션을 이용할 수 있도록 만들어야 한다. 그렇기 때문에 항상 접근성을 준수하여 스크린리더 UX 개선에 신경 쓰는 개발을 해야 한다. 스크린리더는 모바일과 데스크톱 동작 방식이 다르다. 해당 게시물은 모바일 스크린리더에 대한 내용이다.
✅ 웹 접근성에 대한 스크린리더를 공부하고 알게 된 점
코드를 마크업을 할 때 사용하는 태그나 속성을 조금만 신경써서 작성하면 스크린리더의 UX가 많이 개선된다는 점을 알게되었다. 장애인과 고령자 등의 사용자들도 편리하게 이용할 수 있는 UX와, 서비스를 제공할 수 있는 코드가 생각보다 어려운 것이 아니라는 점이다. 그러므로 누구나 차별 없이 서비스를 이용할 수 있는 개발을 위해 앞으로 더 관심을 가지고 신경쓰도록 해야겠다.
📌 접근성 키워드
- 장애를 가진 사람, 고령자 등 누구나 동등하게 쓸 수 있는
- 시멘틱 HTML
- WAI-ARIA
- 스크린리더
- 키보드 인터랙션
📌 알게된 개념
- Accessible Name
- 스크린리더가 포커스했을 때 읽는 값
- author > contents
- wai-aria 속성
- Role
- role 마다 기대되는 동작 ex. role="button"은 "버튼"을 붙여 읽음
- 시멘틱 태그는 암묵적인 role을 가지고 있다. ex) <button>
- Children Presentaional
- 자식요소의 Accessible Name을 모아서 contents로 (끊어 읽는 문제 해결)
- 자식요소를 스크린리더가 읽지 않도록
- <img>와 alt
- alt="" 빈값을 주면 스크린리더는 읽지 않음
- alt 명시하지 않으면 src 읽음
- 선택/ 취소
- role="checkbox" - 체크박스
- aria-checked="true|false" - 선택됨/ 해제됨
- 모달창- 다이어로그
- role="dialog" :사용자가 상호작용할 수 있는 대화상자
- aria-hidden: true - dialog 외에는 요소 포커스할 수 없음
- 소리없는 토스트
- role="alert" - 내용 읽어줌
1. 스크린리더가 앱을 읽는 방법
<did>스크린리더</div>
<div>
<div>
<div>커피</div>
<div>빵</div>
<div>버터</div>
</div>
<div>라따뚜이<div>
<div>2023</div>
🔊읽는 순서: 스크린리더 ➡️ 커피 ➡️ 빵 ➡️ 버터 (↩️ 좌측으로 스와이프 )➡️ 빵➡️ 버터➡️ 라따뚜이 ➡️ 2023
- 움직이는 단위: 요소(HTML Element)로 탐색
- 순차 탐색: 손가락을 좌우로 스와이프 하면 앞뒤로 이동
- 스크린 터치: 터치한 영역에 있는 요소를 선택
- 로터(스크린리더 유틸리티)를 이용한 탐색: 머리말/ 단어/ 글자 단위로 이동 가능
1-2. Accessible Name
- 스크린리더가 요소를 포커스 했을 때 읽는 값
- author: 특별한 속성을 사용해서 정하는 값
- aria-label, aria-labelledby, alt(<img>)
- contents: 요소의 텍스트값
☑️ 우선순위 : author > contents
☑️ Accessible Name은 author와 contens 둘 중에 하나로 결정된다.
<div>접근성을 중요하다</div>contents 🔊 접근성은 중요하다
<div aria-label="A11Y는 중요하다">
접근성을 중요하다
</div>author 🔊 A11Y=는 중요하다
1-3. Role
- 스크린리더가 요소를 어떤 방식으로 다룰지 결정하는 속성
- Role마다 기대되는 스크린리더 동작이 있다.
- 예) role="button"
- 요소의 Accessible Name을 읽은 뒤 "버튼"을 붙여 읽음
- 자식 요소의 Accessible Name을 모아서 contents로 사용
시멘틱 태그와 Role
- 시멘틱 태그는 암시적으로 Role을 갖고 있다.
- <button> role="button"
- <a>: role="link"
- <input type="checkbox">: role="checkbox"
- etc...
<button>
클릭하기
</button>
<!-- = 동일하다 -->
<button role="button">
클릭하기
</button>🔊 클릭하기 버튼
1-4. Children Presentational
- 특정 Role이 가진 특징
- 자식 요소의 Accessible Name을 모아서 요소의 contents로 사용
- 자식 요소를 스크린리더가 읽지 않도록 한다.
- 시각 사용자가 묶어서 이해하는 정보를 스크린리더가 끊어읽는 문제를 해결할 때 유용함✅
<div role="button">
<span>커피</span>
<span>빵</span>
<span>버터</span>
</div> 🔊 커피 빵 버터, 버튼
☑️ 여기서 contents는 "커피+빵+버터"
☑️ 위 div 태그는 별도의 author를 가지고 있지 않기 때문에 Accessible Name은 contents인 "커피+빵+버터"가 된다.
☑️ 그리고 role은 Accessible Name을 읽은 뒤 "버튼"을 붙여 읽기 때문에 "커피 빵 버터 버튼"이라고 읽는다.
3. 케이스 스터디
Case 1: 정체를 알 수 없는 버튼
📌 <img>와 alt
- 시각에 어려움이 있는 경우 이미지를 이해할 수 없다.
- 이미지가 전달하는 정보를 alt에 명시해서 스크린리더가 읽을 수 있게 해야 한다.
- alt에 빈 값("")을 주면 스크린리더가 읽지 않는다(Accessible Name이 ""이 됨)
- alt를 명시하지 않으면 스크린리더가 src를 읽으므로 반드시 alt를 명시해야 한다.
<button>
/*<img src="이미지 연결" alt="">*/
<img
src="이미지 연결"
alt="라따뚜이는 요리사"
>
</button>🔊 버튼
🔊 "라따뚜이는 요리사, 버튼"
☑️ <button>의 contents: 자식들의 Accessible Name의 합이다.
- button은 암묵적으로 role을 가진다.
- children presentaional 특징을 갖기 때문에 자식 요소의 Accessible Name을 모아서 contents로 갖는다.
✅ 문제점과 해결
- 정체를 알 수 없었던 버튼이 alt만 명시해줬을 뿐인데 어떤 버튼인지 알수 있게 된다.
Case 2: 대체 텍스트가 없는 이미지
(의미가 없는 이미지)
<img src="/라따뚜이.png">🔊 "라따뚜이.png, 이미지"
<img src="/라따뚜이.png" alt="">🔊 ...
✅ 문제점과 해결
- alt에 "" 빈값을 주면 스크린리더는 읽지 않는다.
Case 3: 버튼일 수도 있고 아닐 수도 있다.
<-- 기존 코드 -->
<div>
<img
src=/이미지 경로"
alt="라따뚜이"
>
<div>
<span>프랑스</span><span>파리</span>
<img src="이미지 경로" alt="">
</div>
<div>
<span>최고의 요리사</span>
<span>레스토랑</span><span>쉐프</span>
</div>
</div>🔊 라따뚜이 / 프랑스/ 파리/ 최고의 요리사/ 레스토랑/ 쉐프
✅ 문제점과 해결
- 위의 버튼이 버튼이라는 것을 모른다.
- role="button"
- 스크린 리더가 하나의 정보를 끊어 읽어서 이해가 어렵다.
- Children Presentaional : <div> 태그를 <button> 태그로 바꿔준다. 그러면 children presentaional 특징 때문에 자식 요소의 Accessible Name을 모아서 읽어준다.
🔊 라따뚜이 프랑스 파리 최고의 요리사 쉐프
Case 4: 선택 / 취소
📌 role="checkbox"
- 스크린리더가 "체크박스"라고 읽음
- aria-checked="true|false"와 함께 사용하면, "선택됨/선택 해제됨"도 읽어줌
- Children Presentational
<-- 선택 안됨 -->
<div
role="checkbox" aria-checked="false"
>
<img src="이미지 경로" alt="">
<span>라따뚜이</span>
<span>파스타</span>
<span>맵게</sapn>
</div>🔊 "라따뚜이 파스타 맵게, 체크상자, 선택 해제됨"
Case 5: 다가갈 수 없는 바텀시트
📌 role="dialog"
- dialog: 사용자가 상호작용할 수 있는 대화상자 (모달창, 팝업)
- aria-modal: 스크린리더가 dialog 밖의 요소에 포커스할 수 없게 만드는 속성
➡️ "true"인 경우 스크린리더가 dialog만 포커스하게 되므로 사용자가 dialog를 잘 인지할 수 있음
➡️ 스크린리더가 구현하지 않은 경우가 있어 직접 구현 필요하다.💡
➡️ TODO: 모달인 dialog가 열렸을 때 dialog 밖을 포커스할 수 없도록 만들기
ㄴ> Dialog 요소 외의 요소를 포커스할 수 없게 만들기(aria-hidden)
ㄴ> 자동으로 포커스가 Dialog로 옮겨가게 됨
📌 aria-hidden
- 요소에 aria-hidden="true"를 명시할 경우, 스크린리더가 해당 요소와 자식을 읽지 않는다.
<div aria-hdden="true">
<span>라따뚜이</span>
</div>🔊 ... (스크린리더는 읽지 않는다.)
<div>
<h1>주문하시겠습니까?</h1>
<h2>등록된 카드를 보여드립니다. 결제하셨나요?</h2>
<div>
<div>
<button>카카오 카드</button>
<button>국민 카드</button>
<-- ...-->
</div>
<-- 바텀 시트 -->
<div>
<div role="dialog">
<-- 시각적으로 보이지 않는 스크린리더용 버튼 추가-->
<button className="sr-only">닫기</button>
<header>어디에서 결제했나요?</header>
<--... -->
</div>
</div>
</body><-- 스크립트 추가-->
function openModal(dialogContainerElement) {
[...document.body.children].forEach(element => {
if (dialogContainerElement !== element) {
element.setAttribute('aria-hidden', 'true');
}
});
}☑️ 모달을 열었을 때 dialog를 제외한 모든 요소에 aria-hidden을 true로 바꾸는 코드다.
<div aria-hidden="true">
<h1>주문하시겠습니까?</h1>
<h2>등록된 카드를 보여드립니다. 결제하셨나요?</h2>
<div>
<div aria-hidden="true">
<button>카카오 카드</button>
<button>국민 카드</button>
<-- ...-->
</div>☑️ 모달창이 열리면 dialog가 아닌 모든 div 태그에 aria-hidden="true"가 추가된다. 그러면 스크린리더가 해당 요소와 자식을 읽지 않는다. 그리고 스크린리더는 aria-hidden="true"가 없는 바텀시트 요소에 포커스가 가게 된다.
Case 6: 소리없는 토스트
스크린리더는 읽지 않고, 눈으로만 확인가능한 상황
📌 role="alert"
- 다음과 같은 상황에서 스크린리더가 해당 요소를 읽어줌
1. 요소가 DOM Tree에 추가되었거나
2. 요소의 자식에 변경사항이 생겼을 때
<div>
최대 2개 추가할 수 있습니다.
</div>🔊 ...
<div role="alert">
최대 2개 추가할 수 있습니다.
</div>🔊 최대 2개 추가할 수 있습니다.
☑️ role="alert"만 추가했을 뿐인데 유용한 정보를 전달하는 방식으로 코드가 바뀌게 된다.
📕공부 출처: https://youtu.be/tKj3xsXy9KM?si=f9nm_TpXz-B3d5yA
'공부 모음집 > WEB' 카테고리의 다른 글
| FireBase란? (+ cloud 서비스란?) (0) | 2024.03.26 |
|---|---|
| SPA (Single Page Application) (2) | 2023.12.05 |
| 💡 브라우저 좌표 (0) | 2023.11.07 |
| Web APIs (0) | 2023.11.07 |
| url 입력과정 (0) | 2023.10.17 |