우연히 예전에 방치한 velog에서 작성한 글이 꽤나 많은 조회수를 낸 사실을 알았다...!
내용을 조금 더 보완해서 티스토리로 다시 가져와본다.
👉 Velog 글 원본 바로가기 - wai-aria 이용해서 탭 UI 만들기
(마크업 개발자 시절) 평소에 사용하던 탭 UI를 유지보수하는 업무 중
동료분이 wai-aria 한 번 이용해보면 어떨까 말씀을 남겨주셔서 관련한 내용을 찾아보게 됐다.
이번 기회에서는 기존에 작성된 dom에 수정을 해야하는 불가피한 상황에 놓여 결국 적용하지 못했지만 다음에 처음부터 탭 UI를 제작하는 경우가 생긴다면 '확실히 접근성 면에서 훨씬 좋겠다' 생각이 들어 기록 겸 확인한 내용을 정리해본다.
1. 기본 구조
기존에 제작한 단순 UI는 다음과 같다.
<div class="tab_wrap">
<ul class="depth1">
<li><button type="button">한글</button></li>
<li><button type="button">영어</button></li>
</ul>
<ul class="depth2">
<li><a href="#">가</a></li>
<li><a href="#">나</a></li>
<li><a href="#">다</a></li>
</ul>
<ul class="depth2">
<li><a href="#">A</a></li>
<li><a href="#">B</a></li>
<li><a href="#">C</a></li>
</ul>
</div>
* 이전에는 depth1에 해당되는 요소에 대해서도 a 태그를 부여했는데 해당 탭을 눌렀을 때 첫 번째 하위 요소(한글>가, 영어>A)로 자동 이동 기능이 있는 경우였기 때문이다. 현 예제에서는 오로지 버튼의 기능만 하고 있기 때문에 button 태그로 수정하여 작성한다.
2. aria-role
탭 UI는 크게 탭버튼 영역과 탭패널로 구분되기 때문에 aria-role를 이용해 각 요소에 역할을 부여한다.
탭 목록(tablist), 탭(tab), 탭 패널(tabpanel)을 각각 적용한다.
<div class="tab_wrap">
<ul class="depth1" role="tablist">
<li role="tab"><button type="button">한글</button></li>
<li role="tab"><button type="button">영어</button></li>
</ul>
<ul class="depth2" role="tabpanel">
<li><a href="#">가</a></li>
<li><a href="#">나</a></li>
<li><a href="#">다</a></li>
</ul>
<ul class="depth2" role="tabpanel">
<li><a href="#">A</a></li>
<li><a href="#">B</a></li>
<li><a href="#">C</a></li>
</ul>
</div>
3. aria-controls / aria-labelledby
이제 탭의 요소들끼리 서로 상호 반응을 할 수 있도록 연결해주는 작업이 필요하다.
tabpanel에 id를 부여하고 각각의 tab에서 연결하도록 aria-controls 속성을 사용한다.
그리고 각 탭에도 id를 부여하고 각각의 tabpanel과 연결하도록 aria-labelledby 속성을 사용한다.
<div class="tab_wrap">
<ul class="depth1" role="tablist">
<li id="kor_tab" role="tab" aria-controls="kor_panel"><button type="button">한글</button></li>
<li id="eng_tab" role="tab" aria-controls="eng_panel"><button type="button">영어</button></li>
</ul>
<ul id="kor_panel" class="depth2" role="tabpanel" aria-labelledby="kor_tab">
<li><a href="#">가</a></li>
<li><a href="#">나</a></li>
<li><a href="#">다</a></li>
</ul>
<ul id="eng_panel" class="depth2" role="tabpanel" aria-labelledby="eng_tab">
<li><a href="#">A</a></li>
<li><a href="#">B</a></li>
<li><a href="#">C</a></li>
</ul>
</div>
4. tabindex / aria-selected / aria-expanded
활성화/비활성화 상태에 따라 각 탭/탭패널에 각기 다른 값을 주어야하는 속성들이 있다.
- aria-expaneded : 기본 값은 false. 활성화 된 탭은 true를 갖는다.
- tabindex : 기본 값은 -1. 활성화 된 탭, 탭패널은 0을 갖는다.
- aria-selected : 기본 값은 false. 활성화 된 탭, 탭패널은 true를 갖는다.
(1) "한글" 탭 활성화 시
<div class="tab_wrap">
<ul class="depth1" role="tablist">
<li id="kor_tab" role="tab" aria-controls="kor_panel" tabindex="0" aria-selected="true" aria-expanded="true"><button type="button">한글</button></li>
<li id="eng_tab" role="tab" aria-controls="eng_panel" tabindex="-1" aria-selected="false" aria-expanded="false"><button type="button">영어</button></li>
</ul>
<ul id="kor_panel" class="depth2" role="tabpanel" aria-labelledby="kor_tab" tabindex="0" aria-selected="true">
<li><a href="#">가</a></li>
<li><a href="#">나</a></li>
<li><a href="#">다</a></li>
</ul>
<ul id="eng_panel" class="depth2" role="tabpanel" aria-labelledby="eng_tab" tabindex="-1" aria-selected="false">
<li><a href="#">A</a></li>
<li><a href="#">B</a></li>
<li><a href="#">C</a></li>
</ul>
</div>
(2) "영어" 탭 활성화 시
<div class="tab_wrap">
<ul class="depth1" role="tablist">
<li id="kor_tab" role="tab" aria-controls="kor_panel" tabindex="-1" aria-selected="false" aria-expanded="false"><button type="button">한글</button></li>
<li id="eng_tab" role="tab" aria-controls="eng_panel" tabindex="0" aria-selected="true" aria-expanded="true"><button type="button">영어</button></li>
</ul>
<ul id="kor_panel" class="depth2" role="tabpanel" aria-labelledby="kor_tab" tabindex="-1" aria-selected="false">
<li><a href="#">가</a></li>
<li><a href="#">나</a></li>
<li><a href="#">다</a></li>
</ul>
<ul id="eng_panel" class="depth2" role="tabpanel" aria-labelledby="eng_tab" tabindex="0" aria-selected="true">
<li><a href="#">A</a></li>
<li><a href="#">B</a></li>
<li><a href="#">C</a></li>
</ul>
</div>
5. jQuery
이제 어떤 속성을 줘야할 지는 파악했다 👀
4에서 작성한 토글 속성에 대해서 눈으로도 파악할 수 있도록
동작을 위한 jQuery를 함께 작성해볼 수 있다.
4에서 작성한 토글과 display값을 변경하도록 하는 on class를 이용하기 위해서 아래와 같이 작성했다.
$(function(){
/* 초기화 : 첫번째 탭이 기본적으로 활성화 되어있도록 */
$(".depth1 li").first().addClass("on").attr("tabindex", "0").attr("aria-selected", "true").attr("aria-expanded", "true");
$(".depth2").first().addClass("on").attr("tabindex", "0").attr("aria-selected", "true");
/* 탭 클릭 시 속성 전환 */
$(".depth1 li").click(function(e){
e.preventDefault();
$(this).addClass("on").attr("tabindex", "0").attr("aria-selected", "true").attr("aria-expanded", "true");
$(this).siblings().removeClass("on").attr("tabindex", "-1").attr("aria-selected", "false").attr("aria-expanded", "false");
$("#" + $(this).attr("aria-controls")).addClass("on").attr("tabindex", "0").attr("aria-selected", "true").attr("aria-expanded", "true");
$("#" + $(this).attr("aria-controls")).siblings(".depth2").removeClass("on").attr("tabindex", "-1").attr("aria-selected", "false");
});
})
6. JavaScript
이전에는 자바스크립트 사용이 미숙해 jQuery에서 글을 마무리했지만 이번에는 JavaScript 코드도 함께 작성한다.
활성화 코드가 초기화에도 반복되는 점을 이용하여 공통으로 사용할 활성화 함수 activateTab()을 선언한 뒤 이용한다.
document.addEventListener("DOMContentLoaded", function() {
// 탭과 패널 활성화 함수
function activateTab(tab) {
// 탭 활성화 : 모든 탭 비활성화 -> 선택 탭 활성화
const allTabs = document.querySelectorAll(".depth1 li");
allTabs.forEach(function(otherTab){
otherTab.classList.remove("on");
otherTab.setAttribute("tabindex", "-1");
otherTab.setAttribute("aria-selected", "false");
otherTab.setAttribute("aria-expanded", "false");
});
tab.classList.add("on");
tab.setAttribute("tabindex", "0");
tab.setAttribute("aria-selected", "true");
tab.setAttribute("aria-expanded", "true");
// 관련 패널 활성화 : 모든 패널 비활성화 -> 선택 패널 활성화
const panelId = tab.getAttribute("aria-controls");
const panel = document.getElementById(panelId);
const allPanels = document.querySelectorAll(".depth2");
allPanels.forEach(function(otherPanel) {
otherPanel.classList.remove("on");
otherPanel.setAttribute("tabindex", "-1");
otherPanel.setAttribute("aria-selected", "false");
});
panel.classList.add("on");
panel.setAttribute("tabindex", "0");
panel.setAttribute("aria-selected", "true");
}
// 초기화 : 첫 번째 탭 활성화
const firstTab = document.querySelector(".depth1 li");
activateTab(firstTab);
// 모든 탭 버튼에 이벤트 리스너 추가
const tabs = document.querySelectorAll(".depth1 li");
tabs.forEach(function(tab) {
tab.addEventListener("click", function(e) {
e.preventDefault();
activateTab(this);
});
});
});
See the Pen wai-aria tab menu_renewal by Sung-ryung (@sryung1225) on CodePen.
참고
탭 UI 적용 사례 - WCAG 2.1 항공사 WAI-ARIA 적용사례
'MARKUP' 카테고리의 다른 글
[HTML] <textarea>의 줄 바꿈 적용에는 디테일이 필요하다 (feat. pre, 정규표현식) (0) | 2024.08.04 |
---|---|
[HTML] 자주 사용하지 않는 HTML5 태그 정리 (0) | 2023.04.28 |