본문 바로가기
정보 접근성/접근성 (A11Y) - 컴포넌트

접근성 준수 컴포넌트 - 탭 버튼

반응형

접근 가능한 탭 버튼을 만드는 것은 모든 사용자가 사용할 수 있도록 하기 위해 여러 모범 사례를 따르는 것입니다. 여기에는 장애가 있는 사용자를 포함합니다. 다음은 WCAG 표준을 준수하는 접근 가능한 탭 버튼을 만드는 가이드입니다:

1. 의미론적 HTML 및 ARIA 역할 사용

보조 기술에 탭 구조를 전달하기 위해 의미론적 HTML 요소와 ARIA (Accessible Rich Internet Applications) 역할을 사용합니다.

<div role="tablist" aria-label="예제 탭">
  <button role="tab" aria-selected="true" aria-controls="panel1" id="tab1">탭 1</button>
  <button role="tab" aria-selected="false" aria-controls="panel2" id="tab2">탭 2</button>
  <button role="tab" aria-selected="false" aria-controls="panel3" id="tab3">탭 3</button>
</div>
<div role="tabpanel" id="panel1" aria-labelledby="tab1">
  <p>탭 1의 내용입니다.</p>
</div>
<div role="tabpanel" id="panel2" aria-labelledby="tab2" hidden>
  <p>탭 2의 내용입니다.</p>
</div>
<div role="tabpanel" id="panel3" aria-labelledby="tab3" hidden>
  <p>탭 3의 내용입니다.</p>
</div>

2-1. 탭 선택 기능 구현

탭 선택에 따른 컨텐츠 변경 및 탭 버튼의 속성 변경 내용을 코드로 작성하도록 합니다. 제이쿼리 등을 활용하면 더 간단하게 작성할 수 있고, 또한 접근성 준수를 위해 꼭 아래 코드의 전체를 적용해야 하는 것은 아닙니다.

document.addEventListener('DOMContentLoaded', function() {
  const tabList = document.querySelector('[role="tablist"]');
  const tabs = tabList.querySelectorAll('[role="tab"]');
  const tabPanels = document.querySelectorAll('[role="tabpanel"]');

  tabs.forEach(tab => {
    tab.addEventListener('click', function() {
      // Deselect all tabs and hide all tab panels
      tabs.forEach(t => {
        t.setAttribute('aria-selected', 'false');
        t.setAttribute('tabindex', '-1');
      });
      tabPanels.forEach(panel => panel.setAttribute('hidden', 'true'));

      // Select the clicked tab and show the associated panel
      tab.setAttribute('aria-selected', 'true');
      tab.removeAttribute('tabindex');
      const panelId = tab.getAttribute('aria-controls');
      document.getElementById(panelId).removeAttribute('hidden');
    });

    tab.addEventListener('keydown', function(event) {
      let newTab;
      switch (event.key) {
        case 'ArrowLeft':
        case 'ArrowUp':
          newTab = tab.previousElementSibling;
          break;
        case 'ArrowRight':
        case 'ArrowDown':
          newTab = tab.nextElementSibling;
          break;
        case 'Home':
          newTab = tabs[0];
          break;
        case 'End':
          newTab = tabs[tabs.length - 1];
          break;
        default:
          return;
      }
      if (newTab) {
        newTab.focus();
        newTab.click();
      }
    });
  });

  // Set up the initial state
  const selectedTab = tabList.querySelector('[aria-selected="true"]');
  if (selectedTab) {
    selectedTab.setAttribute('tabindex', '0');
    document.getElementById(selectedTab.getAttribute('aria-controls')).removeAttribute('hidden');
  } else {
    // If no tab is selected, select the first one by default
    tabs[0].setAttribute('aria-selected', 'true');
    tabs[0].setAttribute('tabindex', '0');
    document.getElementById(tabs[0].getAttribute('aria-controls')).removeAttribute('hidden');
  }
});

2-2. 키보드 상호작용 관리 (선택)

탭 버튼이 키보드로 탐색할 수 있도록 합니다. 사용자는 화살표 키를 사용하여 탭 간을 탐색하고 Enter 또는 Space 키를 사용하여 탭을 활성화할 수 있어야 합니다. 이 부분은 선택적으로 진행해도 되는 이유는 스크린리더가 클릭 이벤트는 자동으로 전용 커맨드로 선택이 가능하게 설정될 수 있기 때문입니다. 

document.querySelectorAll('[role="tab"]').forEach(tab => {
  tab.addEventListener('keydown', e => {
    let newTab;
    switch (e.key) {
      case 'ArrowLeft':
      case 'ArrowUp':
        newTab = tab.previousElementSibling || tab.parentElement.lastElementChild;
        break;
      case 'ArrowRight':
      case 'ArrowDown':
        newTab = tab.nextElementSibling || tab.parentElement.firstElementChild;
        break;
      case 'Enter':
      case ' ':
        activateTab(tab);
        break;
      default:
        return;
    }
    newTab.focus();
  });

  tab.addEventListener('click', () => activateTab(tab));
});

function activateTab(tab) {
  document.querySelectorAll('[role="tab"]').forEach(t => {
    t.setAttribute('aria-selected', false);
    document.getElementById(t.getAttribute('aria-controls')).hidden = true;
  });
  tab.setAttribute('aria-selected', true);
  document.getElementById(tab.getAttribute('aria-controls')).hidden = false;
  tab.focus();
}

3. 시각적 포커스 표시 제공

키보드로 탐색하는 사용자를 위해 포커스 표시가 눈에 띄도록 합니다.

[role="tab"]:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}

4. ARIA 속성 사용

ARIA 속성을 사용하여 탭과 해당하는 탭 패널 간의 관계를 정의하고, 탭의 선택 상태를 나타냅니다.

  • role="tablist": 탭 세트를 정의하는 컨테이너입니다.
  • role="tab": 각 탭을 정의합니다.
  • aria-controls: 연관된 탭 패널을 식별합니다.
  • aria-selected: 현재 선택된 탭을 나타냅니다.
  • role="tabpanel": 탭의 콘텐츠 패널을 정의합니다.
  • aria-labelledby: 패널을 해당 탭과 연결합니다.

5. 적절한 헤딩 구조 보장

각 탭 패널 내부에 적절한 헤딩을 사용하여 사용자가 콘텐츠 구조를 이해할 수 있도록 합니다.

<div role="tabpanel" id="panel1" aria-labelledby="tab1">
  <h2>탭 1의 내용</h2>
  <p>탭 1의 내용입니다.</p>
</div>

6. 상태 변경 시 접근성 유지

상태 변경(예: 탭 패널 표시/숨기기)이 접근 가능하도록 합니다. 예를 들어, hidden 속성을 사용하여 비활성 탭 패널을 숨깁니다.

접근 가능한 탭 예제

<div role="tablist" aria-label="예제 탭">
  <button role="tab" aria-selected="true" aria-controls="panel1" id="tab1">탭 1</button>
  <button role="tab" aria-selected="false" aria-controls="panel2" id="tab2">탭 2</button>
  <button role="tab" aria-selected="false" aria-controls="panel3" id="tab3">탭 3</button>
</div>
<div role="tabpanel" id="panel1" aria-labelledby="tab1">
  <h2>탭 1의 내용</h2>
  <p>탭 1의 내용입니다.</p>
</div>
<div role="tabpanel" id="panel2" aria-labelledby="tab2" hidden>
  <h2>탭 2의 내용</h2>
  <p>탭 2의 내용입니다.</p>
</div>
<div role="tabpanel" id="panel3" aria-labelledby="tab3" hidden>
  <h2>탭 3의 내용</h2>
  <p>탭 3의 내용입니다.</p>
</div>

이러한 모범 사례를 따르면 접근 가능하고 사용자 친화적인 탭 버튼을 만들어 보조 기술을 사용하는 사용자도 효과적으로 콘텐츠와 상호작용할 수 있습니다.

반응형
❤️ 외주/과외 문의
🖥️ 클라우드 메뉴판 : 디지털팝