접근 가능한 메뉴 시스템 구축
게시 됨: 2022-03-10편집자 주 : 이 기사는 원래 Inclusive Components에 게재되었습니다. 유사한 포괄적인 구성 요소 기사에 대해 더 알고 싶다면 Twitter에서 @inclusicomps를 팔로우하거나 RSS 피드를 구독하십시오. Patreon에서 inclusive-components.design을 지원하면 강력한 인터페이스 구성 요소의 가장 포괄적인 데이터베이스를 사용할 수 있습니다.
분류가 어렵습니다. 게를 예로 들어 보겠습니다. 소라게, 도자기 게, 말굽 게는 분류학적으로 말해서 진정한 게가 아닙니다. 그러나 그것이 "게" 접미사를 사용하는 것을 멈추지 않습니다. 시간이 지남에 따라 그리고 발암 이라고 하는 과정 덕분에 진짜 게가 진짜 게와 더 가깝게 진화할 때 더 혼란스러워집니다. 과거 소라게로 추정되는 왕게의 경우가 그렇다. 그들의 껍질의 크기를 상상해보십시오!
디자인에서 우리는 종종 다른 것에 같은 이름을 부여하는 같은 실수를 범합니다. 비슷해 보이지만 겉모습은 기만적일 수 있습니다. 이것은 구성 요소 라이브러리의 명확성에 불행한 영향을 줄 수 있습니다. 포함의 관점에서 의미론적으로나 행동적으로 부적절한 구성 요소의 용도를 변경할 수도 있습니다. 사용자는 한 가지를 기대하고 다른 것을 얻을 것입니다.
"드롭다운"이라는 용어는 고전적인 예를 나타냅니다. <select>
요소의 <option>
세트와 탐색 하위 메뉴를 구성하는 JavaScript 공개 링크 목록을 포함하여 인터페이스에서 많은 것이 "드롭다운"됩니다. 같은 이름; 아주 다른 것들. (물론 어떤 사람들은 이것을 "풀다운"이라고 부르지만 그것에 대해 이야기하지는 맙시다.)
옵션 집합을 구성하는 드롭다운을 종종 "메뉴"라고 하며 여기에서 이에 대해 이야기하고 싶습니다. 우리는 진정한 메뉴를 고안할 것이지만 그 과정에서 사실이 아닌 메뉴에 대해 할 말이 많습니다.
퀴즈로 시작하겠습니다. 그림 메뉴의 탐색 모음에서 링크 상자가 매달려 있습니까?
정답은 아니오, 진정한 메뉴가 아닙니다.
탐색 스키마가 링크 목록으로 구성된다는 것은 오랜 관습입니다. 거의 오래된 규칙에 따라 하위 탐색이 중첩된 링크 목록으로 제공되어야 합니다. 위에서 설명한 구성 요소의 CSS를 제거하면 파란색과 Times New Roman을 제외하고 다음과 같은 내용이 표시되어야 합니다.
의미상으로 말하면, 링크의 중첩 목록은 이 컨텍스트에서 정확합니다. 탐색 시스템은 실제로 목차이며 이것이 목차가 구조화되는 방식입니다. 우리가 "메뉴"라고 생각하게 만드는 유일한 것은 중첩 목록의 스타일과 마우스를 올리거나 초점을 맞췄을 때 나타나는 방식입니다.
그것이 일부가 잘못되어 WAI-ARIA 의미 체계를 추가하기 시작하는 곳입니다: aria-haspopup="true"
, role="menu"
, role="menuitem"
등 . 다음 두 가지 이유가 있습니다.
- ARIA 메뉴는 탐색용이 아니라 애플리케이션 동작용으로 지정됩니다. 데스크탑 애플리케이션의 메뉴 시스템을 상상해 보십시오.
- 최상위 링크는 링크 로 사용할 수 있어야 합니다. 즉, 메뉴 버튼처럼 작동하지 않습니다.
(2) 관련: 하위 메뉴가 있는 탐색 영역을 탐색할 때 "최상위 수준" 링크(그림의 "상점")에 마우스를 올리거나 초점을 맞추면 각 하위 메뉴가 나타날 것으로 예상할 수 있습니다. 이렇게 하면 하위 메뉴가 표시되고 자체 링크가 포커스 순서로 배치됩니다. 필요한 동안 하위 메뉴의 모양을 유지하기 위해 포커스 및 흐림 이벤트를 캡처하는 JavaScript의 약간의 도움으로 키보드를 사용하는 사람은 각 계층의 각 링크를 차례로 탭할 수 있어야 합니다.
aria-haspopup="true"
속성을 사용하는 메뉴 버튼은 이와 같이 작동하지 않습니다. 클릭 시 활성화되며 비밀 메뉴를 표시하는 것 외에 다른 목적은 없습니다.
그림과 같이 해당 메뉴가 열려 있는지 닫혀 있는지 여부를 aria-expanded
로 전달해야 합니다. 포커스가 아닌 클릭 시에만 이 상태를 변경해야 합니다. 사용자는 일반적으로 단순한 포커스 이벤트에 대한 명시적인 상태 변경을 기대하지 않습니다. 우리의 탐색 시스템에서 상태는 실제로 변경되지 않습니다. 그것은 단지 스타일링 트릭입니다. 행동적으로, 우리는 그러한 표시/숨기기 속임수가 발생하지 않은 것처럼 탐색을 통해 탭할 수 있습니다.
탐색 하위 메뉴 문제
탐색 하위 메뉴(또는 일부의 경우 "드롭다운")는 마우스나 키보드로 잘 작동하지만 터치에 관해서는 그렇게 뜨겁지 않습니다. 이 예에서 처음으로 최상위 "쇼핑" 링크를 누르면 하위 메뉴를 열고 링크를 따라가도록 지시하는 것입니다.
여기에는 두 가지 가능한 해결 방법이 있습니다.
- 전체 WAI-ARIA 메뉴 의미 및 동작에서 최상위 링크(
e.preventDefault()
) 및 스크립트의 기본 동작을 방지합니다. - 각 최상위 대상 페이지에 하위 메뉴에 대한 대안으로 목차가 있는지 확인하십시오.
(1) 이전에 언급했듯이 이러한 종류의 의미와 동작은 링크가 주제 컨트롤인 이 컨텍스트에서 예상되지 않기 때문에 불만족스럽습니다. 또한 사용자는 최상위 페이지가 있는 경우 더 이상 해당 페이지로 이동할 수 없습니다.
참고: 터치 장치는 어떤 장치입니까?
"이것은 훌륭한 솔루션은 아니지만 터치 인터페이스용으로만 추가할 것"이라고 생각하고 싶을 것입니다. 문제는 장치에 터치 스크린이 있는지 어떻게 감지합니까?
"작은 화면"을 "터치 활성화"와 동일시해서는 안 됩니다. 박물관용 터치 디스플레이를 만드는 사람들과 같은 사무실에서 일했기 때문에 주변에서 가장 큰 화면 중 일부는 터치 스크린이라는 것을 확신할 수 있습니다. 듀얼 키보드 및 터치 입력 노트북도 점점 더 많이 보급되고 있습니다.
같은 토큰으로 모든 작은 장치는 아니지만 많은 장치가 터치 장치입니다. 인클루시브 디자인에서는 가정을 할 여유가 없습니다.
해상도 (2)는 모든 입력의 사용자에게 "대체"를 제공한다는 점에서 더 포괄적이고 강력합니다. 그러나 여기에서 대체 용어에 대한 무서운 인용문은 실제로 페이지 내 콘텐츠 테이블이 탐색을 제공하는 우수한 방법이라고 생각하기 때문에 상당히 의도적입니다.
수상 경력에 빛나는 정부 디지털 서비스 팀은 이에 동의하는 것으로 보입니다. Wikipedia에서도 본 적이 있을 것입니다.
목차
목차는 관련 페이지 또는 페이지 섹션에 대한 탐색이며 <nav>
요소, 목록 및 그룹 레이블 지정 메커니즘을 사용하여 의미상 기본 사이트 탐색 영역과 유사해야 합니다.
<nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->
메모
- 이 예에서는 드롭다운 하위 메뉴에 있는 것처럼 각 섹션이 자체 페이지라고 상상합니다.
- 이러한 "쇼핑" 페이지의 구조가 동일해야 하며 이 "제품" 목차는 동일한 위치에 있어야 합니다. 일관성은 이해를 지원합니다.
- 목록은 항목을 그룹화하고 화면 판독기의 합성 음성과 같은 보조 기술 출력에서 항목을 열거합니다.
-
<nav>
는aria-labelledby
labelledby를 사용하여 표제에 의해 재귀적으로 레이블이 지정됩니다. 이것은 "제품 탐색"이 탭 으로 지역에 들어갈 때 대부분의 스크린 리더에서 발표된다는 것을 의미합니다. 또한 "제품 탐색"이 화면 판독기 요소 인터페이스에서 항목화되어 사용자가 지역으로 직접 이동할 수 있음을 의미합니다.
한 페이지에 모두
스크롤하기가 너무 길고 힘들지 않고 모든 섹션을 한 페이지에 맞출 수 있다면 더욱 좋습니다. 각 섹션의 해시 식별자에 연결하기만 하면 됩니다. 예를 들어 href="#waffle-irons"
는 다음을 가리켜야 합니다. .
<nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->
( 참고: 일부 브라우저는 연결된 페이지 조각에 실제로 포커스를 보내는 데 좋지 않습니다. 대상 조각에 tabindex="-1"
을 배치하면 이 문제가 해결됩니다.)
사이트에 많은 콘텐츠가 있는 경우 콘텐츠 테이블 "메뉴"를 자유롭게 사용하여 표현되는 신중하게 구성된 정보 아키텍처가 불안정하고 다루기 힘든 드롭다운 시스템보다 훨씬 더 좋습니다. 반응형을 만드는 것이 더 쉬울 뿐만 아니라 이를 수행하는 데 필요한 코드가 적을 뿐만 아니라 상황이 더 명확해집니다. 드롭다운 시스템은 구조를 숨기고 목차는 그대로 드러냅니다.
Government Digital Service의 gov.uk를 비롯한 일부 사이트에는 목차에 불과한 색인(또는 "주제") 페이지가 있습니다. 인기 있는 정적 사이트 생성기 Hugo가 기본적으로 이러한 페이지를 생성한다는 것은 매우 강력한 개념입니다.
정보 아키텍처는 포함의 큰 부분입니다. 잘못 구성된 사이트는 기술적으로 원하는 만큼 준수할 수 있지만 여전히 많은 사용자, 특히 인지 장애가 있거나 시간이 촉박한 사용자를 소외시킵니다.
탐색 메뉴 버튼
가짜 탐색 관련 메뉴에 대해 이야기하는 동안 탐색 메뉴 버튼에 대해 이야기하지 않는 것이 좋습니다. 세 줄로 된 "햄버거" 또는 "내비콘" 아이콘으로 표시된 것을 거의 확실히 보았을 것입니다.
축소된 정보 아키텍처와 단 하나의 탐색 링크 계층에도 불구하고 작은 화면의 공간은 프리미엄입니다. 버튼 뒤에 탐색을 숨기면 뷰포트에 기본 콘텐츠를 위한 더 많은 공간이 있습니다.
탐색 버튼은 지금까지 우리가 연구한 진정한 메뉴 버튼에 가장 가까운 것입니다. 클릭 시 메뉴의 가용성을 토글하는 목적이 있으므로 다음을 수행해야 합니다.
- 링크가 아닌 버튼으로 자신을 식별합니다.
- 해당 메뉴의 확장 또는 축소 상태를 식별합니다(엄밀히 말하면 링크 목록일 뿐입니다).
점진적 향상
그러나 우리 자신보다 앞서지 맙시다. 점진적인 향상을 염두에 두고 이것이 JavaScript 없이 어떻게 작동하는지 고려해야 합니다.
개선되지 않은 HTML 문서에서는 버튼으로 할 수 있는 일이 많지 않습니다(제출 버튼을 제외하고는 여기에서 달성하려는 것과 밀접한 관련이 없습니다). 대신 탐색으로 연결되는 링크로 시작해야 할까요?
<a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>
링크와 탐색 사이에 많은 콘텐츠가 없으면 링크를 갖는 것이 의미가 없습니다. 사이트 탐색은 거의 항상 소스 순서의 맨 위에 나타나야 하므로 필요하지 않습니다. 따라서 JavaScript가 없는 경우 탐색 메뉴는... 일종의 탐색이어야 합니다.
<nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>
초기 상태에서 버튼을 추가하고 ( hidden
속성을 사용하여) 탐색을 숨겨 이를 향상시킵니다.
<nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>
어떤 오래된 브라우저는 hidden
을 지원하지 않으므로 CSS에 다음을 입력해야 합니다. display: none
은 보조 기술에서 메뉴를 숨기고 포커스 순서에서 링크를 제거하는 것과 동일한 효과를 가지지 않기 때문에 문제를 해결합니다.
[hidden] { display: none; }
물론 오래된 소프트웨어를 지원하기 위해 최선을 다하는 것은 포괄적인 설계 행위입니다. 일부는 업그레이드할 수 없거나 업그레이드할 의사가 없습니다.
놓기
많은 사람들이 잘못 생각하는 부분은 버튼을 영역 외부 에 배치하는 것입니다. 이것은 바로 가기를 사용하여 <nav>
로 이동하는 스크린 리더 사용자가 비어 있음을 알게 되므로 별로 도움이 되지 않습니다. 화면 판독기에서 목록을 숨기면 다음과 같은 상황이 발생합니다.
<nav> </nav>
상태를 전환하는 방법은 다음과 같습니다.
var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });
아리아 컨트롤
Aria-controls Is Poop에서 쓴 것처럼 화면 판독기 사용자가 제어 요소에서 제어 요소로 탐색하는 데 도움을 주기 위한 aria-controls
속성은 JAWS 화면 판독기에서만 지원됩니다. 따라서 단순히 의존할 수 없습니다.
요소 간에 사용자를 안내하는 좋은 방법이 없으면 대신 다음 중 하나가 참인지 확인해야 합니다.
- 확장된 목록의 첫 번째 링크는 버튼 다음의 포커스 순서로 다음 위치에 있습니다(이전 코드 예제에서와 같이).
- 첫 번째 링크는 목록을 표시할 때 프로그래밍 방식으로 초점을 맞춥니다.
이 경우 (1)을 권장합니다. 포커스를 버튼으로 다시 옮기고 어떤 이벤트에서 그렇게 할 것인지에 대해 걱정할 필요가 없기 때문에 훨씬 간단합니다. 또한 현재 사용자에게 초점이 다른 곳으로 이동될 것임을 경고하는 조치가 없습니다. 우리가 곧 논의할 실제 메뉴에서 이것은 aria-haspopup="true"
의 작업입니다.
aria-controls
을 사용하는 것은 화면 판독기의 판독을 더 장황하게 만든다는 점을 제외하고는 그다지 해를 끼치지 않습니다. 그러나 일부 JAWS 사용자는 이를 예상할 수 있습니다. 목록의 id
를 암호로 사용하여 적용하는 방법은 다음과 같습니다.
<nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>
메뉴 및 메뉴 항목 역할
진정한 메뉴(WAI-ARIA 의미에서)는 menu
역할(컨테이너용)과 일반적으로 menuitem
자식(다른 자식 역할이 적용될 수 있음)을 사용하여 자신을 식별해야 합니다. 이러한 부모 및 자식 역할은 함께 작동하여 보조 기술에 정보를 제공합니다. 메뉴 의미 체계를 갖도록 목록을 보강하는 방법은 다음과 같습니다.
<ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>
탐색 메뉴가 "true" 메뉴처럼 작동하기 시작했기 때문에 이러한 메뉴가 없어야 합니까?
짧은 대답은 다음과 같습니다. 긴 대답은 아니오입니다. 목록 항목에는 링크가 포함되어 있고 menuitem
요소는 대화식 자손을 갖도록 의도되지 않았기 때문입니다. 즉, 메뉴 의 컨트롤입니다.
물론 우리는 role="presentation"
또는 role="none"
(동등한 것)을 사용하여 <li>
의 목록 의미를 억제하고 각 링크에 menuitem
역할을 배치할 수 있습니다. 그러나 이것은 암시적 링크 역할을 억제합니다. 즉, 다음 예는 "홈, 링크" 또는 "홈, 메뉴 항목, 링크"가 아닌 "홈, 메뉴 항목"으로 발표됩니다. ARIA 역할은 단순히 HTML 역할을 재정의합니다.
<!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>
우리는 사용자가 링크를 사용하고 있으며 링크 동작을 예상할 수 있다는 것을 알기를 원하므로 이것은 좋지 않습니다. 내가 말했듯이 진정한 메뉴는 (JavaScript 기반) 애플리케이션 동작을 위한 것입니다.
우리에게 남은 것은 일종의 하이브리드 구성 요소입니다. 이것은 진정한 메뉴는 아니지만 최소한 사용자에게 aria-expanded
상태 덕분에 링크 목록이 열려 있는지 여부를 알려줍니다. 이것은 탐색 메뉴에 대해 완벽하게 만족스러운 패턴입니다.
참고: <select>
요소
처음부터 반응형 디자인에 참여했다면 좁은 뷰포트에 대해 탐색이 <select>
요소로 압축된 패턴을 기억할 것입니다.
우리가 논의한 체크박스 기반 토글 버튼과 마찬가지로 추가 스크립팅 없이 의도한 대로 작동하는 기본 요소를 사용하는 것은 효율성과 특히 모바일에서 성능을 위해 좋은 선택입니다. 그리고 <select>
요소 는 우리가 곧 구성할 버튼 트리거 메뉴와 유사한 의미를 가진 일종의 메뉴입니다.
하지만 체크박스 토글 버튼과 마찬가지로 단순히 선택을 하는 것이 아니라 입력과 관련된 요소를 사용하고 있습니다. 이것은 많은 사용자에게 혼란을 야기할 수 있습니다. 특히 이 패턴은 JavaScript를 사용하여 선택한 <option>
이 링크처럼 작동하도록 하기 때문입니다. 이로 인해 발생하는 컨텍스트의 예기치 않은 변경은 WCAG의 3.2.2 입력 시(레벨 A) 기준에 따라 실패로 간주됩니다.
진정한 메뉴
거짓 메뉴와 유사 메뉴에 대해 논의했으므로 이제 진정한 메뉴 버튼으로 열리고 닫히는 진정한 메뉴를 만들어야 할 때입니다. 여기에서 버튼과 메뉴를 간단히 "메뉴 버튼"이라고 부르겠습니다.
그러나 우리의 메뉴 버튼은 어떤 점에서 사실일까요? 글쎄, 그것은 주제 응용 프로그램에서 옵션을 선택하기 위한 메뉴 구성 요소가 될 것이며, 이러한 도구에 대해 일반적인 것으로 간주되는 모든 예상 의미와 해당 동작을 구현합니다.
이미 언급했듯이 이러한 규칙은 데스크톱 응용 프로그램 디자인에서 비롯됩니다. ARIA 어트리뷰션 및 JavaScript 기반 포커스 관리는 이를 완전히 모방하는 데 필요합니다. ARIA의 목적 중 일부는 웹 개발자가 네이티브 세계에서 구축된 사용성 규칙을 위반하지 않고 풍부한 웹 경험을 만들 수 있도록 돕는 것입니다.
이 예에서 우리는 우리의 애플리케이션이 일종의 게임이나 퀴즈라고 상상할 것입니다. 메뉴 버튼을 통해 사용자는 난이도를 선택할 수 있습니다. 모든 의미 체계가 있는 메뉴는 다음과 같습니다.
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>
메모
-
aria-haspopup
속성은 단순히 버튼이 메뉴를 분비한다는 것을 나타냅니다. 눌렀을 때 사용자가 "팝업" 메뉴로 이동한다는 경고 역할을 합니다(포커스 동작은 곧 다룰 예정입니다). 그 값은 변경되지 않습니다. 항상true
로 유지됩니다. - 버튼 내부의
<span>
에는 검은색 아래쪽을 가리키는 작은 삼각형에 대한 유니코드 포인트가 포함되어 있습니다. 이 규칙은aria-haspopup
이 비시각적으로 무엇을 하는지 시각적으로 나타냅니다. 즉, 버튼을 누르면 그 아래에 무언가가 표시됩니다.aria-hidden="true"
속성은 스크린 리더가 "아래를 가리키는 삼각형" 또는 이와 유사한 것을 표시하지 못하도록 합니다.aria-haspopup
덕분에 비시각적 컨텍스트에서는 필요하지 않습니다. -
aria-haspopup
속성은aria-expanded
로 보완됩니다. 이것은true
과false
값 사이를 토글하여 메뉴가 현재 열려 있는(확장된) 상태인지 닫힌(접힌) 상태인지 사용자에게 알려줍니다. - 메뉴 자체는 (적절하게 명명된)
menu
역할을 합니다.menuitem
역할을 가진 하위 항목이 필요합니다.menu
요소의 직계 자식일 필요는 없지만 이 경우에는 단순함을 위해 있습니다.
키보드 및 포커스 동작
대화형 컨트롤을 키보드에 액세스할 수 있게 하려면 올바른 요소를 사용하는 것이 가장 좋습니다. 여기에서 <button>
요소를 사용하기 때문에 HTMLButtonElement 인터페이스에 지정된 대로 Enter 및 Space 키 입력 시 클릭 이벤트가 발생한다고 확신할 수 있습니다. 또한 버튼 관련 disabled
속성을 사용하여 메뉴 항목을 비활성화할 수 있음을 의미합니다.
그러나 메뉴 버튼 키보드 상호 작용에는 더 많은 것이 있습니다. 다음은 WAI-ARIA Authoring Practices 1.1을 기반으로 구현하려는 모든 포커스 및 키보드 동작에 대한 요약입니다.
메뉴 버튼에서 Enter , Space 또는 ↓ | 메뉴를 엽니다. |
↓ 메뉴 항목에 | 포커스를 다음 메뉴 항목으로 이동하거나 마지막 항목에 있는 경우 첫 번째 메뉴 항목으로 이동합니다. |
↑ 메뉴 항목에 | 포커스를 이전 메뉴 항목으로 이동하거나 첫 번째 메뉴 항목에 있는 경우 마지막 메뉴 항목으로 이동합니다. |
↑ 메뉴 버튼에서 | 열려 있으면 메뉴를 닫습니다. |
메뉴 항목에서 Esc | 메뉴를 닫고 메뉴 버튼에 초점을 맞춥니다. |
화살표 키를 사용하여 메뉴 항목 간에 포커스를 이동하는 이점은 메뉴 밖으로 이동할 때 Tab 키가 유지된다는 것입니다. 실제로 이것은 사용자가 메뉴를 종료하기 위해 모든 메뉴 항목을 이동할 필요가 없음을 의미합니다. 특히 메뉴 항목이 많은 경우 사용성이 크게 향상되었습니다.
tabindex="-1"
을 적용하면 Tab 에서 메뉴 항목에 초점을 맞출 수 없지만 화살표 키에서 키 입력을 캡처할 때 프로그래밍 방식으로 요소에 초점을 맞추는 기능은 유지됩니다.
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>
개방 방식
건전한 API 설계의 일부로 다양한 이벤트를 처리하기 위한 메서드를 구성할 수 있습니다.
예를 들어, open
메소드는 aria-expanded
값을 "true"로 전환하고 메뉴의 hidden 속성을 false
로 변경하고 비활성화되지 않은 메뉴의 첫 번째 menuitem
에 초점을 맞춰야 합니다.
MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }
사용자가 포커스가 있는 메뉴 버튼 인스턴스에서 아래쪽 키를 누르면 이 메서드를 실행할 수 있습니다.
this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));
또한 이 스크립트를 사용하는 개발자는 이제 프로그래밍 방식으로 메뉴를 열 수 있습니다.
exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();
참고: 체크박스 해킹
필요한 경우가 아니면 JavaScript를 사용하지 않는 것이 좋습니다. HTML 및 CSS 위에 세 번째 기술을 포함하는 것은 필연적으로 시스템의 복잡성과 취약성을 증가시킵니다. 그러나 JavaScript 없이 모든 구성 요소를 만족스럽게 구축할 수 있는 것은 아닙니다.
메뉴 버튼의 경우 "자바스크립트 없이 작동"하려는 열의가 체크박스 해킹으로 이어졌습니다. CSS를 사용하여 메뉴 요소의 가시성을 토글하기 위해 숨겨진 확인란의 선택(또는 선택 취소) 상태가 사용되는 곳입니다.
/* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }
스크린 리더 사용자에게 체크박스 역할과 체크 상태는 이 맥락에서 의미가 없습니다. 이것은 체크박스에 role="button"
을 추가하여 부분적으로 극복할 수 있습니다.
<input type="checkbox" role="button" aria-haspopup="true">
불행히도 이것은 암시적 검사 상태 통신을 억제하여 JavaScript가 없는 상태 피드백을 박탈합니다.
그러나 aria-expanded
를 스푸핑하는 것은 가능합니다. 레이블에 아래와 같이 두 개의 범위만 제공하면 됩니다.
<input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded</span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">▾</span> </label>
이것들은 모두 visual visually-hidden
지지만, 우리가 어떤 상태에 있는지에 따라 스크린 리더에게도 하나만 숨겨집니다. 즉, 오직 하나만 display: none
을 가지며 이것은 현존하는 (그러나 통신되지는 않은) 체크된 상태에 의해 결정됩니다:
/* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }
이것은 영리하고 모든 것이지만 우리가 논의한 예상 포커스 동작은 JavaScript 없이는 구현할 수 없기 때문에 메뉴 버튼은 여전히 불완전합니다.
이러한 동작은 일반적이고 예상된 것이므로 버튼을 더 유용하게 사용할 수 있습니다. 그러나 JavaScript 없이 메뉴 버튼을 구현해야 하는 경우 가능한 한 거의 비슷합니다. 이전에 다룬 컷다운 탐색 메뉴 버튼이 JavaScript 자체에 의존 하지 않는 메뉴 콘텐츠(예: 링크)를 제공한다는 점을 고려하면 이 접근 방식이 적합한 옵션일 수 있습니다.
재미를 위해 JavaScript가 없는 탐색 메뉴 버튼을 구현하는 codePen이 있습니다.
CodePen에서 Heydon(@heydon)의 Pen Navigation 메뉴 버튼 예제 no JS를 참조하세요.
( 참고: Space 만 메뉴를 엽니다.)
"선택" 이벤트
일부 메서드를 실행하면 리스너를 설정할 수 있도록 이벤트가 발생해야 합니다. 예를 들어 사용자가 메뉴 항목을 클릭할 때 choose
이벤트를 내보낼 수 있습니다. 이벤트의 detail
속성에 인수를 전달할 수 있는 CustomEvent
를 사용하여 이를 설정할 수 있습니다. 이 경우 인수("choice")는 선택한 메뉴 항목의 DOM 노드가 됩니다.
MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }
우리가 이 메커니즘으로 할 수 있는 모든 종류의 일이 있습니다. 아마도 menuFeedback
의 id
로 설정된 라이브 영역이 있을 것입니다.
<div role="alert"></div>
이제 리스너를 설정하고 이벤트 내부에 비밀 정보로 라이브 영역을 채울 수 있습니다.
exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
메뉴 항목이 선택되면 스크린 리더 사용자는 "You selected [menu item's label]"라는 음성을 듣게 됩니다. 라이브 영역(여기에서 role=“alert”
속성으로 정의됨)은 콘텐츠가 변경될 때마다 스크린 리더에서 콘텐츠를 알립니다. 라이브 영역은 필수는 아니지만 메뉴를 선택하는 사용자에 대한 응답으로 인터페이스에서 발생할 수 있는 일의 예입니다.
지속적인 선택
모든 메뉴 항목이 영구 설정을 선택하는 것은 아닙니다. 많은 것들은 눌렀을 때 인터페이스에서 무언가가 일어나도록 하는 표준 버튼처럼 작동합니다. 그러나 난이도 메뉴 버튼의 경우 현재 난이도 설정인 마지막 설정을 표시하고 싶습니다.
aria-checked="true"
속성은 menuitem
대신 menuitemradio
역할을 하는 항목에 대해 작동합니다. 두 번째 항목이 선택된( set ) 향상된 마크업은 다음과 같습니다.
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>
많은 플랫폼의 기본 메뉴는 체크 표시를 사용하여 선택한 항목을 나타냅니다. 약간의 추가 CSS를 사용하여 문제 없이 이를 수행할 수 있습니다.
[role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }
스크린 리더를 실행하고 메뉴를 탐색하는 동안 이 선택된 항목에 초점을 맞추면 "check mark, Medium menu item, checked" 와 같은 안내 메시지가 표시됩니다.
체크된 menuitemradio
로 메뉴를 열 때의 동작은 약간 다릅니다. 메뉴의 첫 번째(활성화된) 항목에 초점을 맞추는 대신 선택한 항목에 초점을 맞춥니다.
이 행동의 이점은 무엇입니까? 사용자(모든 사용자)에게 이전에 선택한 옵션이 표시됩니다. 다양한 증분 옵션(예: 줌 레벨 세트)이 있는 메뉴에서 키보드로 조작하는 사람들은 조정을 수행할 수 있는 최적의 위치에 배치됩니다.
스크린 리더에서 메뉴 버튼 사용하기
이 비디오에서는 Voiceover 스크린 리더와 Chrome에서 메뉴 버튼을 사용하는 방법을 보여 드리겠습니다. 이 예제에서는 menuitemradio
, aria-checked
및 포커스 동작이 논의된 항목을 사용합니다. 널리 사용되는 스크린 리더 소프트웨어 전반에 걸쳐 유사한 경험을 기대할 수 있습니다.
Github의 포괄적인 메뉴 버튼
Kitty Giraudel과 저는 제가 설명한 API 기능 등을 사용하여 메뉴 버튼 구성 요소를 만드는 작업을 함께 했습니다. 액세스 가능한 모달 대화 상자인 11y-대화 상자에서 수행한 작업을 기반으로 했기 때문에 이러한 많은 기능에 대해 Hugo에게 감사의 말을 전합니다. Github 및 NPM에서 사용할 수 있습니다.
npm i inclusive-menu-button --save
또한 Kitty는 당신의 취향에 맞는 React 버전을 만들었습니다.
체크리스트
- 탐색 메뉴 시스템에서 ARIA 메뉴 의미론을 사용하지 마십시오.
- 콘텐츠가 많은 사이트에서는 중첩된 드롭다운 기반 탐색 메뉴에서 구조를 숨기지 마십시오.
-
aria-expanded
를 사용하여 버튼 활성화 탐색 메뉴의 열림/닫힘 상태를 나타냅니다. - 해당 탐색 메뉴가 열거나 닫는 버튼 다음에 초점 순서가 맞는지 확인하십시오.
- JavaScript가 없는 솔루션을 추구하기 위해 사용성을 희생하지 마십시오. 허영심입니다.