tabindex 사용

tabindex로 DOM 순서 수정

Dave Gash
Dave Gash
Meggin Kearney
Meggin Kearney

네이티브 요소의 DOM 위치에서 제공하는 기본 탭 순서는 편리하지만, 탭 순서를 수정해야 하는 경우가 있으며 HTML에서 요소를 물리적으로 이동하는 것이 항상 최적의 해결책은 아니며 실현 가능한 솔루션도 아닙니다. 이러한 경우 tabindex HTML 속성을 사용하여 요소의 탭 위치를 명시적으로 설정할 수 있습니다.

브라우저 지원

  • 1
  • 12
  • 1.5
  • 4 이하

소스

tabindex는 모든 요소에 적용될 수 있지만(모든 요소에 꼭 필요한 것은 아님) 정수 값의 범위를 사용합니다. tabindex를 사용하면 포커스 가능한 페이지 요소의 명시적인 순서를 지정하고, 포커스 불가능한 요소를 탭 순서에 삽입하고, 탭 순서에서 요소를 삭제할 수 있습니다. 예를 들면 다음과 같습니다.

tabindex="0": 일반적인 탭 순서에 요소를 삽입합니다. Tab 키를 눌러 요소에 포커스를 둘 수 있고 focus() 메서드를 호출하여 요소에 포커스를 둘 수 있습니다.

<custom-button tabindex="0">Press Tab to Focus Me!</custom-button>

Tab 키를 눌러 나에게 집중

tabindex="-1": 일반적인 탭 순서에서 요소를 삭제하지만 focus() 메서드를 호출하여 여전히 요소에 포커스를 둘 수 있습니다.

// TODO: DevSite - Code sample removed as it used inline event handlers

// TODO: DevSite - 인라인 이벤트 핸들러를 사용하여 코드 샘플 삭제됨

tabindex="5": tabindex가 0보다 크면 요소가 일반적인 탭 순서 앞으로 이동합니다. tabindex가 0보다 큰 요소가 여러 개 있다면 탭 순서는 0보다 큰 최저 값부터 시작해서 계속 진행됩니다. 0보다 큰 tabindex를 사용하는 것은 안티패턴으로 간주됩니다.

<button>I should be first</button>
<button>And I should be second</button>
<button tabindex="5">But I jumped to the front!</button>

헤더, 이미지, 문서 제목과 같은 비입력 요소의 경우 특히 그렇습니다. 이러한 종류의 요소에 tabindex를 추가하면 오히려 역효과를 낳습니다. 가능하면 DOM 시퀀스가 논리적인 탭 순서를 제공하도록 소스 코드를 정렬하는 것이 좋습니다. tabindex를 사용하는 경우 버튼, 탭, 드롭다운, 텍스트 필드와 같은 커스텀 대화형 컨트롤로 제한하세요. 이러한 컨트롤은 사용자가 입력을 제공할 것으로 기대할 수 있는 요소입니다.

스크린 리더에는 tabindex가 없으므로 사용자가 중요한 콘텐츠를 누락하지 않아도 됩니다. 이미지와 같이 매우 중요한 콘텐츠라도 사용자가 상호작용할 수 있는 대상이 아니라면 포커스 가능하게 만들 이유가 없습니다. 잠시 후 다룰 적절한 alt 속성 지원을 제공하면 스크린 리더 사용자는 이미지 콘텐츠를 계속 이해할 수 있습니다.

페이지 수준에서 포커스 관리

다음은 tabindex가 유용할 뿐만 아니라 필요한 시나리오입니다. 모두 동시에 표시되지는 않지만 다양한 콘텐츠 섹션이 포함된 강력한 단일 페이지를 빌드하고 있을 수 있습니다. 이러한 페이지에서는 탐색 링크를 클릭하면 페이지를 새로고침하지 않고도 표시되는 콘텐츠가 변경될 수 있습니다.

이 경우 선택된 콘텐츠 영역을 식별하고 tabindex에 -1을 지정하여 자연스러운 탭 순서에 표시되지 않도록 하고 focus 메서드를 호출할 수 있습니다. 포커스 관리라고 하는 이 기술은 사용자가 인지한 컨텍스트를 사이트의 시각적 콘텐츠와 동기화된 상태로 유지합니다.

구성요소에서 포커스 관리

페이지에서 무언가를 변경할 때 포커스를 관리하는 것은 중요하지만, 예를 들어 맞춤 구성요소를 빌드하는 경우와 같이 컨트롤 수준에서 포커스를 관리해야 하는 경우도 있습니다.

네이티브 select 요소를 고려하세요. 기본 포커스를 받을 수 있지만 포커스가 설정되면 화살표 키를 사용하여 추가 기능 (선택 가능한 옵션)을 노출할 수 있습니다. 맞춤 select 요소를 빌드하는 경우 키보드에 주로 의존하는 사용자가 여전히 컨트롤과 상호작용할 수 있도록 이와 동일한 종류의 동작을 노출하는 것이 좋습니다.

<!-- Focus the element using Tab and use the up/down arrow keys to navigate -->
<select>
    <option>Aisle seat</option>
    <option>Window seat</option>
    <option>No preference</option>
</select>

어떤 키보드 동작을 구현할지 알기 어려울 수 있지만, 참조할 수 있는 유용한 문서가 있습니다. 액세스 가능한 리치 인터넷 애플리케이션 (ARIA) 작성 방법 가이드에는 구성요소의 유형과 구성요소가 지원하는 키보드 작업의 종류가 나열되어 있습니다. ARIA에 관해서는 나중에 더 자세히 다루겠지만, 지금은 이 가이드를 사용하여 새로운 구성요소에 키보드 지원을 추가해 보겠습니다.

라디오 버튼 모음과 비슷하면서도 각자의 고유한 모양과 동작이 적용된 새로운 맞춤 요소를 만들고 있을 수 있습니다.

<radio-group>
    <radio-button>Water</radio-button>
    <radio-button>Coffee</radio-button>
    <radio-button>Tea</radio-button>
    <radio-button>Cola</radio-button>
    <radio-button>Ginger Ale</radio-button>
</radio-group>

어떤 종류의 키보드 지원이 필요한지 확인하려면 ARIA 작성 방법 가이드를 참고하세요. 섹션 2에는 디자인 패턴 목록이 포함되어 있습니다. 이 목록에는 새 요소와 가장 근접하게 일치하는 기존 구성요소인 라디오 그룹 특성 표가 있습니다.

표에서 볼 수 있듯이 지원해야 하는 일반적인 키보드 동작 중 하나는 위쪽/아래쪽/왼쪽/오른쪽 화살표 키입니다. 이 동작을 새 구성요소에 추가하기 위해 이동 tabindex라는 기법을 사용합니다.

라디오 버튼에 관한 W3C 사양 발췌 부분입니다.

현재 활성 상태인 하위 요소를 제외한 모든 하위 요소의 tabindex를 -1로 설정하면 이동 tabindex가 작동합니다.

<radio-group>
    <radio-button tabindex="0">Water</radio-button>
    <radio-button tabindex="-1">Coffee</radio-button>
    <radio-button tabindex="-1">Tea</radio-button>
    <radio-button tabindex="-1">Cola</radio-button>
    <radio-button tabindex="-1">Ginger Ale</radio-button>
</radio-group>

그런 다음 구성요소는 키보드 이벤트 리스너를 사용하여 사용자가 누르는 키를 확인합니다. 이때 이전에 포커스를 맞춘 하위 요소의 tabindex를 -1로 설정하고 포커스를 둘 하위 요소의 tabindex를 0으로 설정한 후 여기에 포커스 메서드를 호출합니다.

<radio-group>
    // Assuming the user pressed the down arrow, we'll focus the next available child
    <radio-button tabindex="-1">Water</radio-button>
    <radio-button tabindex="0">Coffee</radio-button> // call .focus() on this element
    <radio-button tabindex="-1">Tea</radio-button>
    <radio-button tabindex="-1">Cola</radio-button>
    <radio-button tabindex="-1">Ginger Ale</radio-button>
</radio-group>

사용자가 마지막 (또는 첫 번째, 포커스 이동 방향에 따라) 하위 요소에 도달하면 첫 번째 (또는 마지막) 하위 요소를 다시 순환하여 포커스를 맞춥니다.

아래에서 완성된 예를 시도해 볼 수 있습니다. DevTools에서 요소를 검사하여 tabindex가 라디오 버튼 사이를 이동하는지 관찰하세요.

커피 콜라 진저 에일

// TODO: DevSite - 인라인 이벤트 핸들러를 사용하여 코드 샘플 삭제됨

GitHub에서 이 요소의 전체 소스를 확인할 수 있습니다.

모달 및 키보드 트랩

포커스를 관리할 때 때로는 피할 수 없는 상황에 직면할 수 있습니다. 포커스를 관리하려고 하고 탭 동작을 캡처하지만, 사용자가 완료될 때까지 탭을 닫지 못하게 하는 자동 완성 위젯을 생각해 보세요. 이를 키보드 트랩이라고 하며 사용자가 매우 실망할 수 있습니다. Web AIM 체크리스트의 섹션 2.1.2에서는 이 문제를 해결하며 키보드 포커스가 특정 페이지 요소 하나에 고정되거나 갇혀서는 안 됩니다라고 명시합니다. 사용자는 키보드만 사용하여 모든 페이지 요소 간에 이동할 수 있어야 합니다.

이상하게도, 모달 창에서와 같이 이 동작이 실제로 바람직할 때가 있습니다. 일반적으로 개발자는 모달이 표시될 때 사용자가 모달 이면의 콘텐츠에 액세스하는 것을 원하지 않습니다. 오버레이를 추가하여 페이지를 시각적으로 가릴 수 있지만 키보드 포커스가 실수로 모달 밖으로 이동하는 것을 막지는 못합니다.

사용자에게 작업을 저장할지 묻는 모달 창

이와 같은 경우에는 임시 키보드 트랩을 구현하여 모달이 표시되는 동안에만 포커스를 트랩하고 모달이 닫힐 때 이전에 포커스가 있었던 항목으로 포커스를 복원하도록 할 수 있습니다.

개발자에게 이 작업을 더 쉽게 할 수 있는 방법에 관한 제안이 몇 가지 있지만(예: <dialog> 요소) 아직 브라우저에서 광범위하게 지원되지는 않습니다.

<dialog>에 관한 자세한 내용은 이 MDN 도움말을 참고하고 모달 창에 관한 자세한 내용은 이 모달 예를 참고하세요.

몇 가지 요소를 포함하는 div와 배경 오버레이를 나타내는 또 다른 div로 표현되는 모달 대화상자를 생각해 보세요. 이 상황에서 임시 키보드 트랩을 구현하는 데 필요한 기본 단계를 살펴보겠습니다.

  1. document.querySelector를 사용하여 모달 및 오버레이 div를 선택하고 참조를 저장합니다.
  2. 모달이 열릴 때 모달이 열렸을 때 포커스가 있었던 요소의 참조를 저장하여 해당 요소에 포커스를 반환할 수 있습니다.
  3. keydown 리스너를 사용하여 모달이 열려 있는 동안 키를 누를 때 키를 가져올 수 있습니다. 배경 오버레이에서 클릭을 수신 대기하고 사용자가 배경 오버레이를 클릭하면 모달을 닫을 수도 있습니다.
  4. 다음으로 모달 내에서 포커스 가능한 요소 컬렉션을 가져옵니다. 첫 번째와 마지막 포커스 가능한 요소는 '센티널' 역할을 하여 포커스를 앞뒤로 반복하여 모달 내에 머무르도록 할 시점을 알 수 있습니다.
  5. 모달 창을 표시하고 포커스 가능한 첫 번째 요소에 포커스를 둡니다.
  6. 사용자가 Tab 또는 Shift+Tab를 누를 때 포커스를 앞뒤로 이동하여 마지막 또는 첫 번째 요소에서 적절히 루프를 실행합니다.
  7. 사용자가 Esc를 누르면 모달을 닫습니다. 이렇게 하면 사용자가 특정 닫기 버튼을 검색하지 않고도 모달을 닫을 수 있고 마우스를 사용하는 사용자에게도 도움이 되므로 매우 유용합니다.
  8. 모달이 닫히면 모달과 배경 오버레이를 숨기고 이전에 저장한 이전에 포커스가 맞춰진 요소로 포커스를 복원합니다.

이 절차를 통해 누구나 효과적으로 사용할 수 있는 유용하고 불편함이 없는 모달 창을 제공할 수 있습니다.

자세한 내용은 이 샘플 코드를 살펴보고 완료된 페이지의 실제 예를 확인하세요.