Upgrade to Pro — share decks privately, control downloads, hide ads and more …

모두를 위한 웹 접근성은 무엇이고, 어떻게 하나요? 💬 🔉

모두를 위한 웹 접근성은 무엇이고, 어떻게 하나요? 💬 🔉

접근성은 장애인을 위한 특수한 경험을 넘어 모든 사람의 폭 넓은 콘텐츠 접근을 지향하는 기술입니다.

웹 접근성이 무엇인지, 웹 접근성 준수와 함께 일관된 UI 제어 경험을 어떻게 설계하는지를 제 실무 경험에 비추어 설명합니다. 따라서 접근성에 대한 막연함을 느끼는 경우 의문이 많이 풀릴 것입니다.

Avatar for Mu-Hun

Mu-Hun PRO

August 24, 2025
Tweet

Resources

원본 슬라이드 링크

https://slides.com/muhun/web-accessibility-for-everyone-what-and-how

발표 당시 사용한 슬라이드 환경입니다. 일부 재생 가능한 데모를 직접 둘러보실 수 있습니다.

More Decks by Mu-Hun

Other Decks in Technology

Transcript

  1. 모두를 위한 웹 접근성 무엇이고, 어떻게 하나요? 💬 🔉 웹

    접근성, 모든 사람의 콘텐츠 접근을 지향하는 기술 김무훈, A11YKR 커뮤니티 https://a11ykr.github.io 원본 자료 링크 · QR1 / 68
  2. 발표자 소개 컴퓨터 공학 전공, 4학년 재학 중 약 3년

    간 스타트업에서 프런트엔드 서비스 개발을 맡았습니다. 재직했던 곳 — 펜슬컴퍼니, 유니크굿컴퍼니, 플라네타리움 (인턴) 개인 기술 블로그 — 모두를 위한 접근성 지원과 웹 표준 동향, 오픈소스 참여에 주목하며 웹 프런트엔드 엔지니어링에 대한 관심을 가지고 있습니다. https://frontend.moe ⬆ URL https://muhun.kim 2 / 68
  3. “ 웹 접근성이 정확히 무엇이고, 어떻게 지원을 하는게 좋을지... 평소

    고려했던 웹 접근성의 가치와 접근성 준수의 경험을 담았습니다. 발표 테마 🤔 3 / 68
  4. 웹의 힘, 누구나 접근 가능한 콘텐츠 “ 웹의 힘은 보편성에

    있습니다. 장애와 관계없이 모두의 접근은 필수적인 측면입니다. – Tim Berners-Lee, 웹의 창시자, Accessibility | Our mission | W3C “ 디지털 접근성이란 사용자의 정신적 또는 신체적 능력에 관계없이 웹사이트, 앱과 의미 있고 동등한 방식으로 상호작용할 수 있도록 제품을 설계하고 빌드하는 것을 의미합니다. — 접근성 원칙 - 디지털 접근성은 어떻게 측정되나요? | web.dev 웹의 힘 = 보편성, 모든 사람이 콘텐츠를 쉽게 인식하고 활용할 권리 지향 🌐✨ 5 / 68
  5. 여러분은 이미 접근성에 대해 고려하고 있습니다. 제품을 만들 때, 첫

    기획 단계에서 어떤 정보를 제공할지 정의 대부분 정보는 사용자가 접근할 수 있는 내용, 이는 접근성 지원의 성공 기준 중 하나 성공기준: "이 성공 기준의 의도는 텍스트가 아닌 콘텐츠로 전달하는 정보를 대체 텍스트를 사용하여 접근 가능하게 만드는 것이다." — 텍스트가 아닌 콘텐츠 | WCAG 2 이해, A11YKR Docs 6 / 68
  6. 여러분은 이미 접근성에 대해 고려하고 있습니다. 제품을 만들 때, 첫

    기획 단계에서 어떤 정보를 제공할지 정의 대부분 정보는 사용자가 접근할 수 있는 내용, 이는 접근성 지원의 성공 기준 중 하나 접근성 지원은 늘 고려하는 사용자 중심 디자인의 다른 관점이라고 해석 가능 성공기준: "이 성공 기준의 의도는 텍스트가 아닌 콘텐츠로 전달하는 정보를 대체 텍스트를 사용하여 접근 가능하게 만드는 것이다." — 텍스트가 아닌 콘텐츠 | WCAG 2 이해, A11YKR Docs 7 / 68
  7. 여러분은 이미 접근성에 대해 고려하고 있습니다. 제품을 만들 때, 첫

    기획 단계에서 어떤 정보를 제공할지 정의 대부분 정보는 사용자가 접근할 수 있는 내용, 이는 접근성 지원의 성공 기준 중 하나 접근성 지원은 늘 고려하는 사용자 중심 디자인의 다른 관점이라고 해석 가능 이 점을 간과하는 것은 "보안을 소홀히 하거거나, 나중에 고려하겠다"와 다를 바 없음 성공기준: "이 성공 기준의 의도는 텍스트가 아닌 콘텐츠로 전달하는 정보를 대체 텍스트를 사용하여 접근 가능하게 만드는 것이다." — 텍스트가 아닌 콘텐츠 | WCAG 2 이해, A11YKR Docs "보안을 소홀히 하거나, 나중에 고려하겠다": 『접근성을 지원한다는 착각 | 뱅크샐러드』 게시글의 했습니다. 첫 문단의 내용을 인용 8 / 68
  8. 때론 심미성 보다, 접근성 고려가 우선 어떻게 보일지 = 심미성

    VS 무엇을 = 정보 접근성 첫번째 사진 코레일 앱 "코레일톡" 예매 화면, 두번째 사진 리뉴얼 된 웹 서비스 예매 화면 열차 항목을 티켓 형태로 재디자인하면서 화면에 표시 되는 개수가 6개에서 3개로, 절반으로 줄어듦 인지 부하 증가: 동일한 정보를 얻기 위해 더 많은 스크롤과 탐색이 필요 효율성 감소: 탐색에 할애하는 시간이 증가 9 / 68
  9. 때론 심미성 보다, 접근성 고려가 우선 어떻게 보일지 = 심미성

    VS 무엇을 = 정보 접근성 첫번째 사진 코레일 앱 "코레일톡" 예매 화면, 두번째 사진 리뉴얼 된 웹 서비스 예매 화면 열차 항목을 티켓 형태로 재디자인하면서 화면에 표시 되는 개수가 6개에서 3개로, 절반으로 줄어듦 인지 부하 증가: 동일한 정보를 얻기 위해 더 많은 스크롤과 탐색이 필요 효율성 감소: 탐색에 할애하는 시간이 증가 비교 기능 저하: 여러 선택지를 같이 비교하기가 어려워짐 10 / 68
  10. 모든 사람이 콘텐츠를 쉽게 인지 · 조작 · 이해할 수

    있는 보편성을 지향하는 접근성 원칙을 POUR라고 부름 인지: 대체 텍스트, 자막, 적은 모션 저시력자를 위한 설정 - 고배율, 고대비 접근성 원칙 이해하기 인지 가능, 작동 가능, 이해 가능, 견고함은 모두 서로 연결됩니다. 삽화 출처 디지털 접근성은 어떻게 측정되나요? | web.dev 11 / 68
  11. 모든 사람이 콘텐츠를 쉽게 인지 · 조작 · 이해할 수

    있는 보편성을 지향하는 접근성 원칙을 POUR라고 부름 인지: 대체 텍스트, 자막, 적은 모션 저시력자를 위한 설정 - 고배율, 고대비 조작 키보드 및 터치 스크린 등 조작 기능의 품질 키보드 초점 순서에 버그가 없는가? 접근성 원칙 이해하기 인지 가능, 작동 가능, 이해 가능, 견고함은 모두 서로 연결됩니다. 삽화 출처 디지털 접근성은 어떻게 측정되나요? | web.dev 12 / 68
  12. 모든 사람이 콘텐츠를 쉽게 인지 · 조작 · 이해할 수

    있는 보편성을 지향하는 접근성 원칙을 POUR라고 부름 인지: 대체 텍스트, 자막, 적은 모션 저시력자를 위한 설정 - 고배율, 고대비 조작 키보드 및 터치 스크린 등 조작 기능의 품질 키보드 초점 순서에 버그가 없는가? 이해 예측 가능한 상호작용과 직관적인 콘텐츠 접근성 원칙 이해하기 인지 가능, 작동 가능, 이해 가능, 견고함은 모두 서로 연결됩니다. 삽화 출처 디지털 접근성은 어떻게 측정되나요? | web.dev 13 / 68
  13. 모든 사람이 콘텐츠를 쉽게 인지 · 조작 · 이해할 수

    있는 보편성을 지향하는 접근성 원칙을 POUR라고 부름 인지: 대체 텍스트, 자막, 적은 모션 저시력자를 위한 설정 - 고배율, 고대비 조작 키보드 및 터치 스크린 등 조작 기능의 품질 키보드 초점 순서에 버그가 없는가? 이해 예측 가능한 상호작용과 직관적인 콘텐츠 견고함: 다양한 입력·출력 장치에 제약 받지 않음 접근성 원칙 이해하기 인지 가능, 작동 가능, 이해 가능, 견고함은 모두 서로 연결됩니다. 삽화 출처 디지털 접근성은 어떻게 측정되나요? | web.dev 14 / 68
  14. 시맨틱 마크업을 잘 설계하면 접근성이 보장 그러나 서드파티 UI 구현이

    필요한 경우, 로 접근성을 관리해야 함 📐 WAI ARIA W3C WAI에서 만든 자주 쓰이는 여러 UI 패턴의 사례가 접근성을 염두하여 잘 소개됨 ARIA 작성 방법 가이드(APG) 접근성 준수하기 16 / 68
  15. WAI ARIA 🧭 = Web Accessibility Initiative – Accessible Rich

    Internet Applications WAI — W3C 산하의 웹 접근성 지원 기관 ARIA — 리치 애플리케이션의 접근성을 지원하는 표준 기술 명세 17 / 68
  16. WAI ARIA 🧭 = Web Accessibility Initiative – Accessible Rich

    Internet Applications WAI — W3C 산하의 웹 접근성 지원 기관 ARIA — 리치 애플리케이션의 접근성을 지원하는 표준 기술 명세 장애를 갖고 있는 사용자가 일반인과 동등한 웹 앱의 사용 경험을 제공하는 것이 목표 목표 달성을 위해 ARIA는 HTML 어트리뷰트를 확장하여 재정의 18 / 68
  17. 😵 💫 이것만 기억해두세요, ARIA = {역할, 상태, 속성} “

    ARIA는 웹 콘텐츠와 웹 애플리케이션을 만드는 방법을 정의하는 역할(Roles)과 상태(States), 속성(Properties)의 집합입니다. — ARIA - Accessibility | MDN ARIA의 제공 기능 19 / 68
  18. ARIA 역할 대부분의 HTML 시멘틱 태그는 이미 고유한 역할 존재

    🖲 <button> ➡ role="button" HTML 시멘틱 태그에 없는 UI 패턴은 역할 재정의하여 표현 🎚 스위치 UI ➡ <button role="switch" aria-checked="false/true">레이블 /button> https://codepen.io/mu- hun/embed/qEOYOyx?default-tab=result 20 / 68
  19. 📋 서드파티 <select/> UI ➡ role=listbox" 옵션 UI ➡ role="option"

    ARIA 역할 재정의 <input type="search" value=" 신발 브랜드" <ul role="listbox" <li role="option"> 스케처* </li> <li role="option"> 뉴발란* </li> <li role="option"> 나이* 1 2 aria-control="search-result" 3 aria-expanded="false"> 4 5 id="search-result"> 6 7 8 9 10 11 12 13 14 </li> 15 </ul> 16 21 / 68
  20. ARIA 상태 HTML에 존재하지 않는 다양한 상태 제공, 두가지 예시

    📂 aria-expanded — 제어하는 복합적인 UI가 펼친 여부 표현 "button", "listbox", "tab" 역할에 쓰임 ✅ aria-selected — 현재 항목이 선택된 여부를 표현 "option", "tab" 역할에 쓰임 더 많은 ARIA 상태 목록은 참고 "상태 탭" | WAI ARIA 1.2 Cheat Sheet - DigitalA11Y 22 / 68
  21. ARIA 속성 🔗 관계 어트리뷰트 (요소 간 연결) 🧭 aria-controls

    → 버튼(제어 트리거)↔ 리스트/팝업 제어 🏷 aria-labelledby → 레이블 연결, 💬 aria-describedby → 설명 연결 🎯 aria-activedescendant → 현재 활성화된 하위 항목 명시, 주로 리스트 박스 UI 패턴에서 사용 📡 동작 속성 (알림/구조) 🔔 aria-live → 시간에 민감 - 타이머, 채팅 로그, 주가 정보 등 aria-level → 계층 구조(H1, H2, H3 등 헤딩/트리 UI) 더 많은 관계 어트리뷰트 목록은 " " 명세 참조 6.6.4 Relationship Attributes, WAI ARIA 1.2 23 / 68
  22. ARIA 속성까지, 최종 마크업 및 데모 <h2 id="brand-label"> 신발 브랜드</h2>

    <div role="combobox" aria-labelledby="brand-label" 1 2 3 aria-expanded="true" 4 5 aria-controls="brand-list" 6 > 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 <ul role="listbox" id="brand-list"> 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 </ul> 19 https://codepen.io/mu- hun/embed/yyYKdgN?default- tab=result <input />의 역할을 "combobox"로 재정의하는 것은 유효하지 않아, 별도의 <div role="combobox" /> 로 감쌌습니다. 24 / 68
  23. ARIA 속성까지, 최종 마크업 및 데모 <h2 id="brand-label"> 신발 브랜드</h2>

    <div role="combobox" aria-labelledby="brand-label" 1 2 3 aria-expanded="true" 4 5 aria-controls="brand-list" 6 > 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 <ul role="listbox" id="brand-list"> 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 </ul> 19 <div role="combobox" aria-expanded="true" aria-controls="brand-list" > <ul role="listbox" id="brand-list"> </ul> <h2 id="brand-label"> 신발 브랜드</h2> 1 2 3 4 aria-labelledby="brand-label" 5 6 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 19 https://codepen.io/mu- hun/embed/yyYKdgN?default- tab=result <input />의 역할을 "combobox"로 재정의하는 것은 유효하지 않아, 별도의 <div role="combobox" /> 로 감쌌습니다. 25 / 68
  24. ARIA 속성까지, 최종 마크업 및 데모 <h2 id="brand-label"> 신발 브랜드</h2>

    <div role="combobox" aria-labelledby="brand-label" 1 2 3 aria-expanded="true" 4 5 aria-controls="brand-list" 6 > 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 <ul role="listbox" id="brand-list"> 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 </ul> 19 <div role="combobox" aria-expanded="true" aria-controls="brand-list" > <ul role="listbox" id="brand-list"> </ul> <h2 id="brand-label"> 신발 브랜드</h2> 1 2 3 4 aria-labelledby="brand-label" 5 6 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 19 <input type="search" value=" 나이*" aria-activedescendant="opt-nik*" <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> <h2 id="brand-label"> 신발 브랜드</h2> 1 2 <div role="combobox" 3 aria-expanded="true" 4 aria-labelledby="brand-label" 5 aria-controls="brand-list" 6 > 7 8 9 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 <ul role="listbox" id="brand-list"> 15 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 </ul> 19 https://codepen.io/mu- hun/embed/yyYKdgN?default- tab=result <input />의 역할을 "combobox"로 재정의하는 것은 유효하지 않아, 별도의 <div role="combobox" /> 로 감쌌습니다. 26 / 68
  25. ARIA 속성까지, 최종 마크업 및 데모 <h2 id="brand-label"> 신발 브랜드</h2>

    <div role="combobox" aria-labelledby="brand-label" 1 2 3 aria-expanded="true" 4 5 aria-controls="brand-list" 6 > 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 <ul role="listbox" id="brand-list"> 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 </ul> 19 <div role="combobox" aria-expanded="true" aria-controls="brand-list" > <ul role="listbox" id="brand-list"> </ul> <h2 id="brand-label"> 신발 브랜드</h2> 1 2 3 4 aria-labelledby="brand-label" 5 6 7 <input type="search" 8 value=" 나이*" 9 aria-activedescendant="opt-nik*" 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 15 <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 19 <input type="search" value=" 나이*" aria-activedescendant="opt-nik*" <li role="option" id="opt-nik*" aria-selected="true"> 나이*</li> <h2 id="brand-label"> 신발 브랜드</h2> 1 2 <div role="combobox" 3 aria-expanded="true" 4 aria-labelledby="brand-label" 5 aria-controls="brand-list" 6 > 7 8 9 10 aria-autocomplete="list" 11 /> 12 </div> 13 14 <ul role="listbox" id="brand-list"> 15 16 <li role="option" id="opt-newbalanc*" aria-selected="false"> 뉴발란* </li> 17 <li role="option" id="opt-skecher*" aria-selected="false"> 스케처*</li> 18 </ul> 19 https://codepen.io/mu- hun/embed/yyYKdgN?default- tab=result 스크린리더 사용 데모 <input / 의 역할을 "combobox"로 재정의하는 것은 유효하지 않아, 별도의 <div role="combobox" /> 로 감쌌습니다. 옵션 이동 시 실제로 포커스가 이동하지 않고, aria-activedescendant 로 AT에게 가상 포커스 정보를 제공 27 / 68
  26. ARIA 사용 전 고려 원칙  🌱 Semantic HTML First:

    기본 HTML 기능을 우선 고려 역할과 기능을 하는 태그 · 어트리뷰트를 찾아 사용  ✅ 필요한 경우만 보완적으로 사용 잘못 사용된 ARIA는 주의가 필요 원래 의도한 맥락과 다르게 해석되어, 접근성이 저하 됨 - <div role="button"> 주문하기 + <button> 주문하기</button> + <div role="tab"> - <h2 role="tab"> 제목 탭</h2> + <h2> 제목 탭</h2> + </div> - <div role="my-role"> 29 / 68
  27. 🚫 잘못된 ARIA 사용은 피하자 “ 위에서 ARIA를 언제 어떻게

    사용하고, 사용하면 안 되는 상황에 대해 잘 설명함 조금 더 친절한 가이드 — 일본어지만 브라우저 번역을 통해 충분히 읽으실 수 있습니다. No ARIA is better than Bad ARIA — Read Me First | APG | WAI | W3C WAI ARIA를 배울 때 정리해 두고 싶은 것 (일본어) 30 / 68
  28. 접근성을 염두에 두고 UI 개발이 가능한가? 아래 항목은 접근성 지원에

    대한 일반적인 오해를 두 명제로 정리한 것  웹 접근성은 장애 사용자만을 위한 특수한 사용자 경험을 지칭하는 기술 분야이다.  따라서 보조 기술(AT) 전용 UI를 별도 구현해야 해서 구현 시간이 더 늘어난다. 저의 답변 32 / 68
  29. 접근성을 염두에 두고 UI 개발이 가능한가? 아래 항목은 접근성 지원에

    대한 일반적인 오해를 두 명제로 정리한 것  웹 접근성은 장애 사용자만을 위한 특수한 사용자 경험을 지칭하는 기술 분야이다.  따라서 보조 기술(AT) 전용 UI를 별도 구현해야 해서 구현 시간이 더 늘어난다. 저의 답변 여러 가지 오해가 있지만, 접근성을 준수하는 것은 충분히 제시간에 달성할 수 있는 목표입니다. 경험상 접근성 인터페이스를 별도로 고려하지 않고, 일반 UI와 함께 설계하는 방식을 따르면 됩니다. 33 / 68
  30. 저는 지난 2023년에 펜슬컴퍼니에서 “글리프“라는 콘텐츠 투고 웹 서비스의 출시

    준비 3개월 전부터 프런트엔드 개발에 참여, 적절히 서비스에 웹 접근성 기술이 지원되도록 노력을 했습니다. 접근성을 함께 고려한 배경 소개 34 / 68
  31. 접근성을 함께 고려한 배경 소개 재직했던 회사에서 공개 허가를 받고

    공유드리는 대화 이력입니다. 동료 디자이너와 색상 대비에 관한 의견을 종종 주고 받았습니다. 저는 오랫동안 접근성에 관한 검토 의견을 자주 제시 FE 동료에게 시멘틱 마크업 준수와 WAI ARIA 활용을 꾸준히 제안, 접근성을 염두한 작업 방식이 사내 컨벤션으로 상당히 수용 35 / 68
  32. 접근성을 함께 고려한 배경 소개 재직했던 회사에서 공개 허가를 받고

    공유드리는 대화 이력입니다. 접근성 개선 및 ARIA 활용에 대한 제안 개발 내역은 모두 오픈소스로 열람할 수 있습니다. 동료 디자이너와 색상 대비에 관한 의견을 종종 주고 받았습니다. 저는 오랫동안 접근성에 관한 검토 의견을 자주 제시 FE 동료에게 시멘틱 마크업 준수와 WAI ARIA 활용을 꾸준히 제안, 접근성을 염두한 작업 방식이 사내 컨벤션으로 상당히 수용 36 / 68
  33. 접근성을 고려하여 UI 색상 변경하기 ♿ 🎨 <input name="price" aria-invalid={invalidPrice}

    input[name="price"][aria-invalid="true"] { border: 1px solid red; background-color: rgb(255, 0, 0, 0.5); } <Tooltip enabled={invalidPrice} placement="top"> 1 <span slot="message" aria-live="assertive" 2 id={errorTooltipId}> 3 {errorMessages.join('<br/>')} 4 </span> 5 6 7 8 aria-describeby={errorTooltipId} 9 inputmode="numeric" 10 </Tooltip> 11 12 <style> 13 14 15 16 17 </style> 18 팝업 형태의 포인트 설정 UI aria-invalid — 입력한 값이 유효하지 않다는 힌트 전달 37 / 68
  34. 접근성을 고려하여 UI 색상 변경하기 ♿ 🎨 <input name="price" aria-invalid={invalidPrice}

    input[name="price"][aria-invalid="true"] { border: 1px solid red; background-color: rgb(255, 0, 0, 0.5); } <Tooltip enabled={invalidPrice} placement="top"> 1 <span slot="message" aria-live="assertive" 2 id={errorTooltipId}> 3 {errorMessages.join('<br/>')} 4 </span> 5 6 7 8 aria-describeby={errorTooltipId} 9 inputmode="numeric" 10 </Tooltip> 11 12 <style> 13 14 15 16 17 </style> 18 input[name="price"][aria-invalid="true"] { border: 1px solid red; background-color: rgb(255, 0, 0, 0.5); } <Tooltip enabled={invalidPrice} placement="top"> 1 <span slot="message" aria-live="assertive" 2 id={errorTooltipId}> 3 {errorMessages.join('<br/>')} 4 </span> 5 <input 6 name="price" 7 aria-invalid={invalidPrice} 8 aria-describeby={errorTooltipId} 9 inputmode="numeric" 10 </Tooltip> 11 12 <style> 13 14 15 16 17 </style> 18 팝업 형태의 포인트 설정 UI aria-invalid — 입력한 값이 유효하지 않다는 힌트 전달 38 / 68
  35. 접근성을 고려하여 UI 색상 변경하기 ♿ 🎨 <Tooltip enabled={invalidPrice} placement="top">

    <span slot="message" aria-live="assertive" id={errorTooltipId}> {errorMessages.join('<br/>')} </span> <input name="price" + class="aria-[invalid='true']:(border-error-900 bg-error-50)" aria-invalid={invalidPrice} aria-describeby={errorTooltipId} inputmode="numeric" </Tooltip> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 오픈소스, 에서 확인 가능 실제로는 Tailwind 환경에서 작업했습니다. penxle/withglyph#1296 39 / 68
  36. Tailwind CSS, ARIA 어트리뷰트 기본 지원 Tailwind는 일부 ARIA 상태에

    대해 예약된 variants로 기본 지원합니다. aria-busy, aria-checked, aria-expanded, aria-pressed, aria-selected 등 지원하지 않은 ARIA 상태의 경우, aria-[invalid="true"] 형태의 표기를 손수 써야 함 ⬆ https://tailwindcss.com/docs/hover-focus-and- other-states#aria-states 40 / 68
  37. 리스트박스 UI 열림과 닫힘을 표현하기 button.expanded { background-color: lightgray; &

    > i.chevron-icon { transform: rotate(0.5turn); } } button > i.chevron-icon { transition: transform 0.5s; } ul { position: absolute; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 열린 상태 닫힌 상태 <Toolbar> <button type="button" aria-expanded={isFontListOpen} class={isFontListOpen ? 'expanded' : ''} onclick={() => isFontListOpen = !isFontListOpen} > 프리텐다드 <i class="chevron-icon" /> </button> <ul role="listbox" hidden= {!isFontListOpen}> <li role="option" data-value=" 프리텐다드" onclick={onSelectFontValue} > 프리텐다드 <i class="selected-icon" /> </li> // 기타 다른 옵션... </ul> </Toolbar> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 보조 기술과 일반 사용자 각각 별도의 인터페이스 구현 ARIA와 별도의 클래스네임을 활용하는 수고 존재 41 / 68
  38. 리스트박스 UI 열림과 닫힘을 표현하기 button.expanded { background-color: lightgray; &

    > i.chevron-icon { transform: rotate(0.5turn); } } button > i.chevron-icon { transition: transform 0.5s; } ul { position: absolute; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 열린 상태 닫힌 상태 <Toolbar> <button type="button" aria-expanded={isFontListOpen} class={isFontListOpen ? 'expanded' : ''} onclick={() => isFontListOpen = !isFontListOpen} > 프리텐다드 <i class="chevron-icon" /> </button> <ul role="listbox" hidden= {!isFontListOpen}> <li role="option" data-value=" 프리텐다드" onclick={onSelectFontValue} > 프리텐다드 <i class="selected-icon" /> </li> // 기타 다른 옵션... </ul> </Toolbar> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 aria-expanded={isFontListOpen} class={isFontListOpen ? 'expanded' : ''} <Toolbar> 1 <button type="button" 2 3 4 onclick={() => isFontListOpen = !isFontListOpen} 5 > 6 프리텐다드 <i class="chevron-icon" /> 7 </button> 8 <ul role="listbox" hidden= {!isFontListOpen}> 9 <li role="option" 10 data-value=" 프리텐다드" 11 onclick={onSelectFontValue} 12 > 13 프리텐다드 14 <i class="selected-icon" /> 15 </li> 16 // 기타 다른 옵션... 17 </ul> 18 </Toolbar> 19 보조 기술과 일반 사용자 각각 별도의 인터페이스 구현 ARIA와 별도의 클래스네임을 활용하는 수고 존재 42 / 68
  39. 열린 상태 닫힌 상태 - button.expanded { + button[aria- expanded='true']

    { background-color: lightgray; & > i.chevron-icon { transform: rotate(0.5turn); } } + button[aria- expanded='true'] + ul[role='listbox'] { + display: block; + } 1 2 3 4 5 6 7 8 9 10 11 열림과 닫힘을 ARIA 주도로 표현하기 <button type="button" aria-expanded={isFontListOpen} - class={isFontListOpen ? 'expanded' : ''} onclick={() => isFontListOpen = !isFontListOpen} > 프리텐다드 <i class="chevron-icon" /> </button> - <ul role="listbox" hidden= {!isFontListOpen}> + <ul role="listbox"> <li role="option" data-value=" 프리텐다드" onclick={onSelectFontValue} > 프리텐다드 <i class="selected-icon" /> </li> // 기타 다른 옵션... <Toolbar> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 </ul> 19 </Toolbar> 20 단일 마크업을 보조 기술과 일반 사용자 양측에 사용 ARIA와 별도의 클래스네임을 활용하는 수고가 줄어듬 접근성 힌트인 ARIA를 사용하면 CSS의 가독성이 향상 43 / 68
  40. 원본 일러스트 출처 - (번역) 구닥다리 공룡을 위한 오늘날의 CSS

    — Steemit Dinosaur Comics 클래스네임은 HTML 의미를 대체할 수 없다. CSS 명세에 따르면, 클래스네임은 HTML과 달리 의미를 담고 있지 않습니다. 따라서 클래스네임은 HTML의 의미를 대체할 수 없습니다. "CSS는 클래스 어트리뷰트에 강력한 기능을 부여하여, 고유한 역할이 거의 없는 엘리먼트(예 HTML의 <div> 및 <span>)를 기반으로 나만의 “문서 언어"를 설계하고 “class” 어트리뷰트를 통해 스타일을 지정할 수 있습니다. 하지만 문서 언어의 구조적 요소는 일반적으로 정해진 의미가 있지만, 사용자가 임의로 정의한 클래스는 그렇지 않을 수 있습니다. 따라서 이러한 관행을 피하는 것이 좋습니다." — “Class Selectors” Selectors Level 3 | W3C 44 / 68
  41. 방법론이지만... 실용적입니다. 💡 ARIA 표준은 시각적 스타일링 목적으로 만들어진 것이

    아님, 세가지 웹 표준 명세 HTML, CSS, WAI ARIA 에서 ARIA를 활용한 의미론적 CSS 작성은 찾을 수 없습니다. 그러나 이 방법론은 의미론적 HTML 원칙을 준수합니다. 여러 웹 표준의 메커니즘을 자산으로 한 실용적인 방법론. 45 / 68
  42. 초점 흐름 설계 원칙 🎯  탭 시퀀스 원칙 ⇥

    Tab, ⇧⇥ Shift+Tab → 입력 간 이동  위젯 내 초점 이동 규칙 아이템 사이 초점 이동 방향키로 제한 Tab 키 입력 시 다른 입력으로 포커스 이동 운영체제에서 보이는 일반적인 GUI 운용 방식을 따른 것 ⇥ Tab, ⇠⇡⇣⇢ 방향키 펜슬컴퍼니의 또 다른 제품 "리더블"의 사이트 생성 단계 페이지 47 / 68
  43. 자세히 설명  탭 시퀀스 원칙 복합적인 UI 위젯은 탭

    순서에 초점 가능한 하위 요소 1개만 포함 Tab, Shift+Tab → 위젯 간 이동  입력 내 초점 이동 규칙 아이템 사이 초점 이동 방향키로 제한 선택 항목만 tabindex="0", 나머지 1 (aka. " ") Tab 키 입력 시 탐색 중인 위젯을 벗어나 다른 입력으로 포커스 이동 운영체제에서 보이는 일반적인 GUI 운용 방식을 따른 것 Roving tabindex 관련하여 키보드 제어 컨설팅을 제공한 오픈소스 PR penxle/readable#1128 일반적인 GUI 운용 방식 "but they are strongly advised to use the same key bindings as similar components in common GUI operating systems as demonstrated in ." — APG Patterns Keyboard Navigation Inside Components | APG 48 / 68
  44. 돌아보기 Roving tabindex 기능 적용하기 <h2 id="brand-label"> 신발 브랜드</h2> <div

    role="combobox" aria-expanded="true" aria-labelledby="brand-label" aria-controls="brand-list" > <input type="search" value=" 나이*" aria-activedescendant="opt-nik*" aria-autocomplete="list" /> <ul role="listbox" id="brand-list"> <li role="option" id="opt-nik*" tabindex="0" aria-selected="true"> 나이*</li> <li role="option" id="opt-newbalanc*" tabindex="-1" aria-selected="false"> 뉴발란*</li> <li role="option" id="opt-skecher*" tabindex="-1" aria-selected="false"> 스케처*</li> </ul> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 https://codepen.io/mu- hun/embed/yyYKdgN? default-tab=result ⇥ Tab, ⇧⇥ Shift+Tab 다음 항목으로 이동하거나, 이전 항목으로 돌아가기 49 / 68
  45. 돌아보기 Roving tabindex 기능 적용하기 <h2 id="brand-label"> 신발 브랜드</h2> <div

    role="combobox" aria-expanded="true" aria-labelledby="brand-label" aria-controls="brand-list" > <input type="search" value=" 나이*" aria-activedescendant="opt-nik*" aria-autocomplete="list" /> <ul role="listbox" id="brand-list"> <li role="option" id="opt-nik*" tabindex="0" aria-selected="true"> 나이*</li> <li role="option" id="opt-newbalanc*" tabindex="-1" aria-selected="false"> 뉴발란*</li> <li role="option" id="opt-skecher*" tabindex="-1" aria-selected="false"> 스케처*</li> </ul> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 value=" 나이*" aria-activedescendant="opt-nik*" tabindex="0" aria-selected="true"> 나이*</li> <h2 id="brand-label"> 신발 브랜드</h2> 1 2 <div role="combobox" 3 aria-expanded="true" 4 aria-labelledby="brand-label" 5 aria-controls="brand-list" 6 > 7 <input type="search" 8 9 10 aria-autocomplete="list" 11 /> 12 <ul role="listbox" id="brand-list"> 13 <li role="option" id="opt-nik*" 14 15 16 <li role="option" id="opt-newbalanc*" tabindex="-1" 17 aria-selected="false"> 뉴발란*</li> 18 <li role="option" id="opt-skecher*" tabindex="-1" 19 aria-selected="false"> 스케처*</li> 20 </ul> 21 </div> 22 https://codepen.io/mu- hun/embed/yyYKdgN? default-tab=result ⇥ Tab, ⇧⇥ Shift+Tab 다음 항목으로 이동하거나, 이전 항목으로 돌아가기 50 / 68
  46. 다양한 UI 패턴에 해당 https://codepen.io/mu-hun/embed/EaVyGgq? default-tab=result https://codepen.io/mu-hun/embed/ogjLJZx? default-tab=result 적용 가능한

    UI 패턴 툴바, 콤보박스, 그리드, 메뉴, 라디오 그룹, 탭, 트리 뷰 입니다. 더 자세한 정보는 " "를 참고 기본 키보드 탐색 규칙, 키보드 인터페이스 개발 | ARIA 작성 방법 가이드 51 / 68
  47. 다양한 UI 패턴에 해당 https://codepen.io/mu-hun/embed/EaVyGgq? default-tab=result https://codepen.io/mu-hun/embed/ogjLJZx? default-tab=result 적용 가능한

    UI 패턴 툴바, 콤보박스, 그리드, 메뉴, 라디오 그룹, 탭, 트리 뷰 입니다. 더 자세한 정보는 " "를 참고 기본 키보드 탐색 규칙, 키보드 인터페이스 개발 | ARIA 작성 방법 가이드 52 / 68
  48. Headless UI에 접근성 지원을 맡기기 접근성 표준에 맞춘 UI를 매번

    직접 구현하는 것은 중복되고, 많은 시간이 소요 🤯 이를 위해 Headless UI라는 툴킷이 등장, 스타일링을 제외한 UI 로직과 접근성 지원만을 제공 : 팀에서 여러 배포판(React, Solid, Vue, Svelte)을 동시에 제공 중 : Adobe에서 관리, 다양한 UI 패턴의 React 구현을 Headless UI로 제공 : Svelte 사용자 커뮤니티에서 운영하는 Headless UI 도구 Ark UI Chakra UI 스스로 사용하고자 만든 도구 React Aria Melt UI 접근성 지원을 고려하지 않더라도, 자주 사용되는 UI 패턴의 보일러플레이트 구현과 의존성 주입(Dependency Injection)을 유연하게 지원하므로 적극 사용을 추천 54 / 68
  49. UI 테스트의 단서, ARIA "aria-control" 같은 ARIA 관계 어트리뷰트를 통해

    각 HTML 엘리먼트 간의 관계를 명확히 표현 가능 관계성 명시는 보조 기술의 접근성 향상만 아니라, UI 테스트를 구성할 때 유용한 힌트로 쓸 수 있음 import { render } from '@testing- library/svelte' import Select from './Select.svelte' const defaultSelectValue = ' 프리텐다드' const rendered = render(Select) // getByRole API 를 이용하여 원하는 두 엘리먼트 쿼리하기 const triggerButton = rendered.getByRole('button') const listbox = rendered.getByRole('listbox') // triggerButton & listbox 관계성 일치하는지 확인 expect(triggerButton).toHaveAttribute('aria- controls', listbox.id) expect(triggerButton).toHaveAttribute( 'aria-activedescendant', makeOptionIdByFontValue(defaultSelectValue) ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 56 / 68
  50. UI 테스트의 단서, ARIA "aria-control" 같은 ARIA 관계 어트리뷰트를 통해

    각 HTML 엘리먼트 간의 관계를 명확히 표현 가능 관계성 명시는 보조 기술의 접근성 향상만 아니라, UI 테스트를 구성할 때 유용한 힌트로 쓸 수 있음 import { render } from '@testing- library/svelte' import Select from './Select.svelte' const defaultSelectValue = ' 프리텐다드' const rendered = render(Select) // getByRole API 를 이용하여 원하는 두 엘리먼트 쿼리하기 const triggerButton = rendered.getByRole('button') const listbox = rendered.getByRole('listbox') // triggerButton & listbox 관계성 일치하는지 확인 expect(triggerButton).toHaveAttribute('aria- controls', listbox.id) expect(triggerButton).toHaveAttribute( 'aria-activedescendant', makeOptionIdByFontValue(defaultSelectValue) ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // getByRole API 를 이용하여 원하는 두 엘리먼트 쿼리하기 const triggerButton = rendered.getByRole('button') const listbox = rendered.getByRole('listbox') // triggerButton & listbox 관계성 일치하는지 확인 expect(triggerButton).toHaveAttribute('aria- controls', listbox.id) expect(triggerButton).toHaveAttribute( 'aria-activedescendant', makeOptionIdByFontValue(defaultSelectValue) ) import { render } from '@testing- library/svelte' 1 import Select from './Select.svelte' 2 3 const defaultSelectValue = ' 프리텐다드' 4 5 const rendered = render(Select) 6 7 8 9 10 11 12 13 14 15 16 17 와 UI 테스트 도구에서 접근성 트리를 바탕으로 한 getByRole 쿼리 사용을 우선적으로 권장 Playwright testing-library Playwright: "To make tests resilient, we recommend prioritizing user-facing attributes and explicit contracts such as ." — testing-library: page.getByRole() Locating elemets, Locators https://testing-library.com/docs/queries/about/#priority 57 / 68
  51. 접근성 트리 aka. 접근성 객체 모델(AOM) name: 쇼핑 role: region

    children: - name: shoppingbox role: internal frame children: - name: shoppingbox role: document children: - name: role: generic children: - name: role: generic children: - name: role: tablist children: - name: 쇼핑 role: tab description: 쇼핑은 광 고영역입니다. children: - name: 쇼핑 role text leaf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 네이버 포털 을 개발자 도구의 접근성 패널로 살펴보면, “region(쇼핑) → tablist → tab…” 등으로 구성된 접근성 트리를 볼 수 있습니다. 쇼핑 섹션 브라우저는 DOM 트리와 11 대응하는 AOM라는 데이터 구조를 생성함 DOM 트리의 역할, 이름, 상태 등 추상화하여 구성 네이버 쇼핑 섹션 컨테이너의 역할은 "region"이고 이름이 "쇼핑"이라면, “영역, 쇼핑” 🔉라고 들려줍니다. 키보드 제어 및 기타 상호작용 모두 접근성 트리 기반 운용 58 / 68
  52. UI 테스트에 접근성 기술 활용하기 🧪 A11YKR 커뮤니티 멤버 탐정토끼님의

    접근성 트리를 응용한 국제화 테스트 자동화 공유 트윗 접근성 트리를 UI 테스트 관점에서 바라본다면, 접근 가능한 정보의 변화만을 추적할 수 있습니다. 두 가지 UI 테스트 방법 Assertion: HTML 요소를 하나씩 쿼리하여 내용 확인 Snapshot: 비트맵 형식의 스크린샷 비교하여 감지 접근성 트리 기반 🌳 트리 데이터 덤프: 평문, YAML 등 형식의 스냅샷으로 저장 Playwright에서 " "라는 기능으로 릴리즈 됨 지난해 11월부터 Aria Snapshots 59 / 68
  53. import { test, expect } from '@playwright/test'; test('Banner contains expected

    elements', async ({ page }) => { await page.goto('https://playwright.dev/'); const banner = page.getByRole('banner'); await expect(banner.getByRole('heading', { level: 1 })).toHaveText( /Playwright enables reliable end-to-end/ ); await expect(banner.getByRole('link').nth(0)); .toHaveText('Get started'); await expect(banner.getByRole('link').nth(1)) .toHaveText('Star microsoft/playwright on GitHub'); await expect(banner.getByRole('link').nth(2)).toHaveText( /[\d]+k\+ stargazers on GitHub/ ); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Assertion Testing – UI 요소 일일이 확인하기 😫 getByRole('link').nth(2).toHaveText( ) 검증을 항목마다 반복합니다. 🥱 https://playwright.dev/ https://playwright.dev/ 60 / 68
  54. Snapshot Testing – 접근성 트리 기반 테스트 📸 import {

    test, expect } from '@playwright/test'; test('Banner contains expected elements', async ({ page }) => { await page.goto('https://playwright.dev/'); const banner = page.getByRole('banner'); await expect(page.getByRole('banner')).toMatchAriaSnapshot(` - banner: - heading /Playwright enables reliable end-to-end/ [level=1] - link "Get started" - link "Star microsoft/playwright on GitHub" - link /[\\d]+k\\+ stargazers on GitHub/ `); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 접근성 트리를 YAML 형식으로 표현하여 스냅샷 테스팅을 지원합니다. 😇 공식 소개 겸 데모 영상 Getting started with ARIA Snapshots - 유튜브 https://playwright.dev/ https://playwright.dev/ 61 / 68
  55. 접근성 트리와 접근성 기술 간 흐름 도식화 다만, 이러한 활용은

    다양한 브라우저와 보조기술(AT) 사이의 지원이 맞물려야 유의미함 aria-busy_attribute (aria) | Accessibility Support aria-autocomplete_attribute (aria) | Accessibility Support 63 / 68
  56. 웹 접근성 준수가 가져다주는 이점 👍  확장성이 뛰어난 HTML

    ⚡  의미론적 CSS 선택자 🎨  UI 테스트에 적합한 접근성 트리 🧪🌲  기계가 읽을 수 있는 문서 🤖 (Machine-Readable Document) 모두가 쉽게 접근할 수 있는 웹 페이지는 기계에게도 동일하게 적용 SEO뿐만 아니라, LLM에도 정보 색인이 더 원활 65 / 68
  57. 접근성의 가치를 지키기, 우리의 역할 “ "효율적인 소프트웨어 개발팀은 이러한

    투쟁에서 정면으로 맞서 싸운다. 소프트웨어를 안전하게 지켜야 할 책임이 있기 때문이다." — 『클린 아키텍처』 중에서 여기서 언급한 "투쟁"은 대립이 아닌 더 나은 방향으로 나아가기 위한 노력 "경사로"는 디지털 접근성과 유사 "경사로"를 만드는 전문가 = 프런트엔드 개발자 위 삽화는 마이클 F. 지앙그레코(Michael F. Giangreco)라는 특수교육 전문가가 2002년에 제작한 유명한 자료입니다. 66 / 68
  58. 추천 자료 – 더 알아보기 위한 레퍼런스 📚 웹 접근성

    학습 자료 : 이 주제에 대해 스스로 처음 기록한 자료. ARIA를 바탕으로 한 의미론적인 CSS 규칙을 설계하는 방법을 소개했습니다. : 정보접근성 인식 개선을 위한 유튜브 채널. 한국지능정보사회진흥원 (NIA)에서 운영하며, 등의 다양한 접근성 사례를 제공합니다. : 이미지 alt 속성의 필요성과 작성 방법을 설명한 국내 블로그 포스트. 시각 콘 텐츠에 대한 대체 텍스트 제공 취지를 이해하는 데 도움이 됩니다. 접근성 기반 UI 설계 : 접근성 기반 UI 패턴 모범사례를 다루는 해외 블로그. 영문 내용이 전자책 으로 출간되었고, 한국어 번역판 이 있습니다. : – HTML에 없는 UI 위젯에 대한 패턴 표준화 연구 및 표준 제안 커뮤니티. 최근 웹 표준에 추가된 되었습니다. 웹 접근성이 UI 설계에 중요한 이유 AOA11Y 접근성 오픈 아카데미 항공사 접근성 사례집 올바른 대체 텍스트 Inclusive Components 『인클루시브 디자인 패턴』(웹액츄얼리코리아) Open UI W3C 커뮤니티 그룹 Popover API가 여기서 제안 67 / 68
  59. ➡ "다 함께 접근성 배웁시다" 발표 자료 자문과 슬라이드 검수에

    A11YKR 커뮤니티의 많은 도움을 받았습니다. 🙇 https://a11ykr.github.io A11YKR 커뮤니티 68 / 68