2022. 3. 6. 08:16ㆍFrontend
Overview
Web.dev에서 소개하는 Accessibility에 대한 내용들을 정리합니다. 모든 내용들을 다 다루지는 않고, 2부에 걸쳐서 개인적으로 중요하다고 생각하는 부분들을 추려서 중점적으로 정리했습니다. 자세한 내용들은 아래 Table of Contents의 링크를 통해 확인하실 수 있습니다.
Table Of Contents
사용자의 다양한 요구 이해 #
사이트 키보드에 접근성 부여 #
- Keyboard access fundamentals
- Use semantic HTML for easy keyboard wins
- Control focus with tabindex
- Style focus
What is Accessibility?
An accessible site is one whose content can be accessed regardless of any user's impairments and whose functionality can also be operated by the most diverse range of users possible.
Accessibility(접근성)이란 유저의 impairments(부상 및 장애 여부)에 상관없이 유저가 원하는 기능을 제공할 수 있는 것을 의미합니다. 크게 다음과 같은 4가지 범주로 구분해볼 수 있습니다.
Vision
- 시력을 완전히 잃은 사용자 뿐 아니라 단순 “저시력자"도 포함하는 개념입니다.
- Screen Magnification, high contrast themes, text-to-speech 등의 기능을 제공할 수 있습니다.
Motor/Dexterity
- keyboard, head or eye-tracking software 등을 제공할 수 있습니다.
Auditory
- captions & transcripts를 제공할 수 있습니다.
Cognitive
- minimize distractions, flashing, heavy animations
- custom colors & styles
- 주의력 결핍 장애(ADHD)등을 포함할 수 있으며 유저에게 커스텀 색상이나 스타일을 제공할 수 있습니다.
Keyboard Access Fundamentals
좋은 접근성을 제공하는 웹 사이트들은 유저가 마우스 클릭이 없이도 키보드의 Tab버튼이나 방향키, Enter 버튼들을 이용해서 원하는 메뉴들에 접근할 수 있으며 원하는 동작들을 수행할 수 있습니다. 이를 제대로 구현하기 위해서는 브라우저가 Focus를 어떻게 처리하는지, Tab 순서를 어떻게 처리하는지를 알아야 할 필요가 있습니다. 아래는 크롬에서 제공하는 문서의 내용을 발췌한 것으로, focus 요소를 어떤 Element에 어떤 순서로 처리하는지에 대한 간단한 가이드라인입니다.
Having a good keyboard navigation strategy for your application creates a better experience for everyone.
- The currently focused element is often indicated by a focus ring
- various browsers style their focus rings differently
- The order in which focus proceeds forward and backward through interactive elements is called the tab order
- Interactive HTML elements like text fields, buttons, and select lists are implicitly focusable
- automatically inserted into the tab order based on their position in the DOM
- These interactive elements also have built-in keyboard event handling
이를 사용해서 개발자가 사이트에 적용할 수 있는 것은 logical tab order 를 잘 활용하는 것입니다. 우선 Focus가 필요하다고 판단되는 Element를 Interactive HTML로 변경하고, 원하는 탭 순서에 맞게 DOM Element를 배치합니다. (이렇게 하지 않고도 아래에서 설명할 TabIndex를 사용하는 방법이 있지만, 브라우저에서 제공하는 기본적인 방법을 사용해서 해결할 수 있다면 가급적 피하는 것이 좋습니다)
- Arrange elements in the DOM to be in logical order
- Correctly set the visibility of offscreen content that should not receive focus
Arrange elements in the DOM to be in logical order
원하는 탭 순서에 맞게 DOM Element를 배치하는 작업을 진행하기 위해서는 가급적 float과 같은 속성을 사용하는 것을 피하는 것이 좋습니다. 아래의 링크에서 logical tab order가 적용된 예시와 그렇지 않은 예시에서 각각 tab순서가 어떻게 매겨지는지를 확인할 수 있습니다. 아래 그림은 illogical tab order가 적용된 예시입니다. (실제 DOM의 위치는 KIWI가 제일 앞섭니다.) 유저는 Peach가 가장 먼저 Tab Focus를 받을 것으로 생각하지만 실제로는 DOM 순서상 가장 앞서는 KIWI가 Focus를 받게 됩니다.
Correctly set the visibility of offscreen content
Sometimes offscreen interactive elements need to be in the DOM but should not be in your tab order. For example, if you have a responsive side-nav that opens when you click a button, the user should not be able to focus on the side-nav when it's closed.
브라우저에서 Focus를 받을 수 있는 Interactive HTML Element들의 Focus 포함 여부를 CSS를 통해서 설정할 수 있습니다. 기본원리는 "스크린에 보이는가"이며, 이와 관련된 CSS 속성인 display와 visibility를 사용해서 설정할 수 있습니다.
아래와 같이 설정하면 Focus Element에 포함되지 않는다.
- display: none
- visibility: hidden
아래와 같이 설정하면 Focus Element에 포함된다.
- display: block
- visibility: visible
Tips
💡 If you can't figure out where the focus on your page is as you're tabbing, open the console and type: document.activeElement. This property will return the element that currently has focus.
Use semantic HTML for easy keyboard wins
By using the correct semantic HTML elements you may be able to meet most or all of your keyboard access needs. That means less time fiddling with tabindex, and more happy users!
위에서 잠깐 언급했던 것처럼 적절한 Semantic HTML을 사용하면 tabIndex 를 의도적으로 설정하지 않도록 좋은 Accessibility를 제공할 수 있습니다. Focus Element에 기본적으로 들어가는 HTML 요소는 아래와 같이 "사용자와 상호작용이 가능한" Interactive HTML 요소들입니다.
- <a>
- <button>
- <input>
- <select>
- <textarea>
- Content Editable feature
<blockquote contenteditable="true">
<p>Edit this content to add your own quote</p>
</blockquote>
<cite contenteditable="true">-- Write your own name here</cite>
Semantic HTML 요소들을 잘 사용하면 모바일에서 제공하는 built-in support를 사용할 수 있습니다. 예를 들어서 <input /> Element에서 제공하는 type(number, email, text...) 등을 잘 사용하면 아래와 같이 모바일에서 적절한 키보드 형태를 제공합니다.
Use button instead of div
A common accessibility anti-pattern is to treat a non-interactive element, like a div or a span, as a button by adding a click handler to it.
위에서 소개한 Semantic HTML이 잘 지켜지지 않는 대표적인 경우가 바로 Button 대신에 Div 태그를 사용하는 경우입니다. 일반적으로 버튼은 아래와 같은 조건을 만족해야 브라우저에서 제공하는 Semantic Button의 기능을 모두 제공할 수 있습니다.
Button Should:
- Be focusable via the keyboard
- Support being disabled
- Support the ENTER or SPACE keys to perform an action
- Be announced properly by a screen reader
💡 button elements have a neat trick called synthetic click activation
→ Enter나 Space를 입력했을 때 Click handler를 트리거한다.
Div 태그에 Clickhandler만 붙이고 스타일만 변경하게 된다면 이렇게 브라우저에서 자동으로 제공하는 synthetic click activation 등의 기능을 사용할 수 없으며, 다음 포스팅에서 살펴보겠지만 Screen Reader에서 사용하는 Accessibility Tree에 Div 요소는 의도된 대로 활성화되지 않고 생략될 가능성이 높기 때문에 스크린 리더를 사용하는 사용자들에게 좋은 UX를 제공할 수 없게 됩니다.
e.g) https://synthetic-click.glitch.me/
Links versus buttons
Another common anti-pattern is to treat links as buttons by attaching JavaScript behavior to them.
또 다른 안티 패턴 중 하나는 link 태그와 button 태그를 혼용해서 사용하는 것입니다. 일반적으로 "해당 페이지"내에서 적용되는 액션을 트리거해야 한다면 button을 사용하고, 새로운 페이지로 "이동"해야 한다면 link를 사용해야 합니다. link 태그는 브라우저의 History API를 사용하기 때문에 button과는 기본적으로 다른 역할(role)을 수행합니다. 즉, 스크린 리더기에 의해 다르게 취급됩니다.
- If clicking on the element will perform an action on the page, use <button>.
- If clicking on the element will navigate the user to a new page then use <a>. This includes single page web apps that load new content and update the URL using the History API.
https://github.com/mdo/wtf-forms/blob/master/wtf-forms.css
TabIndex Focusing
가능하면 사용자 정의 버전을 구축하는 대신 내장 HTML 요소를 사용하세요. 예들 들어, <button>은 스타일 지정이 매우 쉽고 이미 완전한 키보드 지원을 제공합니다. tabindex를 관리하거나 ARIA로 의미 체계를 추가할 필요가 없습니다.
가급적이면 Tab Focus를 사용하기 위해서 Semantic HTMl 요소를 사용하는 것이 좋지만 div와 span 같은 태그에도 의도적으로 Tab Focus가 적용되도록 하고 싶을 때가 있습니다. 이때는 tabIndex를 사용합니다.
- tabindex="0"을 사용하여 자연스러운 탭 순서로 요소를 삽입하세요
- tabindex="-1"을 사용하여 요소를 제거합니다. 예를 들면 다음과 같습니다.
- 이렇게 하면 자연스러운 탭 순서에서 요소가 제거되지만 focus() 메서드를 호출하여 요소에 계속 초점을 맞출 수 있습니다.
- tabindex="-1"을 요소에 적용해도 하위 요소에는 영향을 미치지 않습니다.
- 0보다 큰 모든 tabindex는 요소를 자연스러운 탭 순서의 맨 앞으로 옮깁니다. tabindex가 0보다 큰 요소가 여러 개 있는 경우 탭 순서는 0보다 큰 가장 낮은 값부터 시작하여 위로 올라갑니다. (Anti-pattern)
- 양수인 경우에 무제한으로 올릴 수 있는게 아니라 32768까지 허용(1부터)
Roving TabIndex
Select와 같은 요소의 경우 Tab Focus가 된 이후에 방향키를 이용해서 요소들을 선택할 수 있는 기능들을 제공하는 것이 좋습니다. 이는 select 태그에 대한 built-in browser support가 아니기 때문에 Javascript를 사용해서 구현해야 합니다. 이를 Roving TabIndex라고 하며, tabIndex와 element.focus() 메서드를 사용하여 구현할 수 있습니다.
A roving tabindex basically sets the tabindex to -1 for all children of the element except the currently focused one.
- 추가적인 키보드 지원을 추가하기 위해서(화살표 위아래 등등) 내장된 select 요소를 고려하는 것이 좋습니다.
- 현재 활성화된 하위 요소를 제외한 모든 하위 요소에 대해 tabIndex를 설정합니다. 이를 Roving TabIndex라고 합니다.
→ 즉 첫번째 아이템에만 tabindex=0을 주어서 focus 되게 한 다음 나머지 요소들의 Focus는 JS를 통해 Control 하는 방식을 의미합니다.
<div role="toolbar" class="toolbar">
<button tabindex="0">Undo</button>
<button tabindex="-1">Redo</button>
<button tabindex="-1">Cut</button>
<button tabindex="-1">Copy</button>
<button tabindex="-1">Paste</button>
</div>
// Copyright 2018 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// It can be really helpful to have constants for keycodes
// That way when you look at your source in a 3 months you won't
// have to remember what keycode 37 means :)
const KEYCODE = {
LEFT: 37,
RIGHT: 39
};
const toolbar = document.querySelector('.toolbar');
toolbar.addEventListener('keydown', onKeyDown);
toolbar.addEventListener('click', onClick);
function onKeyDown(event) {
switch (event.keyCode) {
case KEYCODE.RIGHT:
event.preventDefault();
focusNextItem();
break;
case KEYCODE.LEFT:
event.preventDefault();
focusPreviousItem();
break;
}
}
function onClick(event) {
// Make sure the clicked item is one of the buttons and
// not something random :)
const buttons = Array.from(toolbar.querySelectorAll('button'));
if (buttons.indexOf(event.target) == -1) {
return;
}
activate(event.target);
}
// Figure out if the current element has a next sibling.
// If so, moving focus to it.
function focusNextItem() {
const item = document.activeElement;
if (item.nextElementSibling) {
activate(item.nextElementSibling);
}
}
// Figure out if the current element has a previous sibling.
// If so, moving focus to it.
function focusPreviousItem() {
const item = document.activeElement;
if (item.previousElementSibling) {
activate(item.previousElementSibling);
}
}
// This is where the roving tabindex magic happens!
function activate(item) {
// Set all of the buttons to tabindex -1
toolbar.querySelectorAll('button').forEach((btn) => btn.tabIndex = -1);
// Make the current button "active"
item.tabIndex = 0;
item.focus();
}
'Frontend' 카테고리의 다른 글
[Web.dev] Network (0) | 2022.03.19 |
---|---|
[Web.dev] Accessibility (2) (0) | 2022.03.19 |
[Web.dev] Chrome DevTools (0) | 2022.02.28 |
[Web.dev] Web Security (2) (0) | 2022.02.18 |
[Web.dev] Web Security (1) (0) | 2022.02.10 |