Кратко
СкопированоГлобальный атрибут, который указывает браузеру, в каком порядке пользователь перемещается между элементами на странице. Может добавить фокус даже для неинтерактивных элементов — параграфов и заголовков.
tabindex
так называется из-за клавиши Tab, которой перемещаются между интерактивными элементами. Это действие ещё часто называют «табать» или «протабать».
Пример
СкопированоСодержимое вкладки добавлено в порядок навигации:
<div role="tabpanel" tabindex="0"> <p>Травоядные животные с коротким хоботом, которые живут в лесу.</p></div>
<div role="tabpanel" tabindex="0"> <p>Травоядные животные с коротким хоботом, которые живут в лесу.</p> </div>
Как пишется
СкопированоЗначение tabindex
— любое целое отрицательное или положительное число. Например, 0, 1 или -1.
0
— элемент в последовательном порядке навигации (или фокуса), но сам порядок определяется браузером.1
или другое положительно число — элемент в порядке навигации без участия браузера. Чем больше значение, тем выше элемент в порядке.-1
или другое отрицательное число — на элемент можно кликнуть, но он не попадает в порядок последовательной навигации. То есть, на нём не сделать фокус с клавиатуры, даже если до этого было можно.
По умолчанию у всех тегов в порядке навигации стоит значение 0
. Это <a>
или <area>
с атрибутом href
, <button>
, <input>
, <select>
, <textarea>
, первый элемент <summary>
внутри <details>
, <iframe>
и <object>
.
Если у нескольких элементов одинаковые положительные значения, они идут по порядку расположения в коде. В этом примере фокус сначала попадёт на ссылку «Усы», потом на «Лапы» и затем на «Хвост».
<!-- Пример для наглядности, не делайте так ❌ --><a href="#mustache" tabindex="1">Усы</a><a href="#paws" tabindex="1">Лапы</a><a href="#tail" tabindex="1">Хвост</a>
<!-- Пример для наглядности, не делайте так ❌ --> <a href="#mustache" tabindex="1">Усы</a> <a href="#paws" tabindex="1">Лапы</a> <a href="#tail" tabindex="1">Хвост</a>
Когда не задаёте элементам tabindex
, браузеры сами выбирают, когда его использовать. Они хорошо с этим справляются без посторонней помощи, но бывают ситуации, когда без tabindex
не обойтись.
Несколько основных правил использования tabindex
:
- визуальный порядок навигации должен совпадать с логическим, то есть как расположены элементы в разметке;
- не изменяйте последовательный порядок навигации без необходимости;
- добавляйте в порядок навигации только действительно важные элементы, у которых нет такой возможности по умолчанию или это надо изменить;
- не добавляйте статические элементы вроде параграфов и списков в порядок навигации за редким исключением;
- не задавайте элементам положительные значения
tabindex
; - дочерние элементы интерактивных родителей не должны быть в порядке навигации — их родитель уже в нём.
Когда нужно убрать интерактивный элемент из порядка навигации, вместо tabindex
лучше использовать атрибуты disabled
или inert
.
Когда использовать
СкопированоЕсли нельзя так просто взять и использовать tabindex
, зачем атрибут разработчикам? Короткий ответ — для сложных ситуаций, когда нужно управлять порядком навигации вручную.
tabindex
помогает сделать удобными сложные компоненты, часто кастомные. Например, модальные окна, древовидные списки, сетки, вкладки и так далее. Также он помогает улучшить пользовательский опыт в формах. К примеру, с помощью tabindex
можно переносить фокус на текст ошибки или важную подсказку к полю.
В панели вкладок контейнеру с содержимым с ролью tabpanel
часто задают tabindex
, чтобы пользователи могли попасть в неё с клавиатуры.
<div role="tablist"> <button role="tab" type="button" id="tab-1" aria-selected="true" aria-controls="tabpanel-1" > Тапиры </button> <div role="tabpanel" id="tabpanel-1" aria-labelledby="tab-1" tabindex="0" > <p>Травоядные животные с коротким хоботом, которые живут в лесу.</p> </div></div>
<div role="tablist"> <button role="tab" type="button" id="tab-1" aria-selected="true" aria-controls="tabpanel-1" > Тапиры </button> <div role="tabpanel" id="tabpanel-1" aria-labelledby="tab-1" tabindex="0" > <p>Травоядные животные с коротким хоботом, которые живут в лесу.</p> </div> </div>
Когда в сложных компонентах управляют фокусом с помощью JavaScript-метода focus
, можно сбросить фокус у интерактивного элемента с помощью tabindex
. Это часто встречается в кастомных комбинированных и древовидных списках.
В комбинированных списках кнопке с треугольником, которая открывает список по клику, задают tabindex
и убирают из порядка навигации.
<label for="input"> Породы собак</label><div> <input role="combobox" id="input" aria-expanded="false" aria-controls="listbox" > <button aria-label="Выбрать породу" aria-expanded="false" aria-controls="listbox" tabindex="-1" ></button></div><ul role="listbox" id="listbox" aria-label="Породы"> <li role="option" id="breed-1"> Алабай </li> <li role="option" id="breed-2"> Афганская борзая </li></ul>
<label for="input"> Породы собак </label> <div> <input role="combobox" id="input" aria-expanded="false" aria-controls="listbox" > <button aria-label="Выбрать породу" aria-expanded="false" aria-controls="listbox" tabindex="-1" ></button> </div> <ul role="listbox" id="listbox" aria-label="Породы" > <li role="option" id="breed-1"> Алабай </li> <li role="option" id="breed-2"> Афганская борзая </li> </ul>
Обычно в сложных компонентах, как в панели вкладок, используют трюк с перемещающимся tabindex
(roving tabindex
). Это когда управляем фокусом вручную с помощью JavaScript и меняем значения tabindex
в зависимости от того, какой элемент сейчас в фокусе.
<div role="tablist"> <!-- Вкладки --> <!-- Содержимое вкладки, которое по умолчанию в фокусе --> <div role="tabpanel" tabindex="0" > <!-- Текст из первой вкладки --> </div> <!-- Содержимое вкладки, которое пока что не в фокусе --> <div role="tabpanel" tabindex="-1" > <!-- Текст из второй вкладки --> </div></div>
<div role="tablist"> <!-- Вкладки --> <!-- Содержимое вкладки, которое по умолчанию в фокусе --> <div role="tabpanel" tabindex="0" > <!-- Текст из первой вкладки --> </div> <!-- Содержимое вкладки, которое пока что не в фокусе --> <div role="tabpanel" tabindex="-1" > <!-- Текст из второй вкладки --> </div> </div>
Как работает перемещающийся tabindex
в сложных компонентах:
- У интерактивного элемента, который в фокусе по умолчанию, должен быть
tabindex
. У пока неактивных элементов без фокуса= "0" tabindex
.= " - 1" - Когда пользователь сделал фокус на другом элементе, для элемента в фокусе по умолчанию устанавливаем
tabindex
, а для нового —= " - 1" tabindex
и фокус при помощи= "0" .focus
.( ) - Если пользователь вышел из компонента, оставляем
tabindex
у последнего элемента, с которым он взаимодействовал.= "0"
tabindex
пригодится и в модальных окнах. Например, когда в окне много текста, первым элементом в фокусе делают первый параграф. Для этого ему тоже задают tabindex
. Благодаря этому, если используете клавиатуру, фокус автоматически окажется на этом параграфе при открытии окна.
<button aria-controls="modal">Принять условия</button><dialog id="modal" aria-labelledby="label"> <h3 id="label">Условия подписки</h3> <p tabindex="-1"> Подписываясь на наш сервис, вы обещаете учить язык не менее 50 часов в день 🦉 </p> <p> Если кажется, что это слишком много, идите в другой сервис. Здесь вам не рады! </p> <p> Смысл начинать учить испанский или китайский, если откладываете его изучение? К чему бесполезные обещания? </p> <form method="dialog"> <button>Принимаю</button> <button>Убегаю</button> </form></dialog>
<button aria-controls="modal">Принять условия</button> <dialog id="modal" aria-labelledby="label"> <h3 id="label">Условия подписки</h3> <p tabindex="-1"> Подписываясь на наш сервис, вы обещаете учить язык не менее 50 часов в день 🦉 </p> <p> Если кажется, что это слишком много, идите в другой сервис. Здесь вам не рады! </p> <p> Смысл начинать учить испанский или китайский, если откладываете его изучение? К чему бесполезные обещания? </p> <form method="dialog"> <button>Принимаю</button> <button>Убегаю</button> </form> </dialog>
Кастомные контейнеры со скроллбаром и CSS-свойством overflow
— ещё один хороший повод задуматься о tabindex
. Без tabindex
его нельзя прокрутить с клавиатуры.
div[role="group"] { height: 9rem; padding: 25px 15px; overflow: auto;}
div[role="group"] { height: 9rem; padding: 25px 15px; overflow: auto; }
<div role="group" tabindex="0"> <h3>Условия хранения данных</h3> <!-- Параграфы с текстом --></div>
<div role="group" tabindex="0" > <h3>Условия хранения данных</h3> <!-- Параграфы с текстом --> </div>
Распространённые ошибки
СкопированоОдна из самых распространённых ошибок — ручной порядок навигации на странице. Это когда у элементов есть tabindex
с положительными значениями. Они даже могут повторять порядок расположения элементов в коде:
<!-- Не делайте так ❌ --><ul> <li> <a href="#mustache" tabindex="1">Усы</a> </li> <li> <a href="#paws" tabindex="2">Лапы</a> </li> <li> <a href="#tail" tabindex="3">Хвост</a> </li></ul>
<!-- Не делайте так ❌ --> <ul> <li> <a href="#mustache" tabindex="1">Усы</a> </li> <li> <a href="#paws" tabindex="2">Лапы</a> </li> <li> <a href="#tail" tabindex="3">Хвост</a> </li> </ul>
Ручной порядок навигации может и отличаться от визуального:
<!-- Не делайте так ❌ --><ul> <li> <a href="#tail" tabindex="3">Хвост</a> </li> <li> <a href="#mustache" tabindex="1">Усы</a> </li> <li> <a href="#paws" tabindex="2">Лапы</a> </li></ul>
<!-- Не делайте так ❌ --> <ul> <li> <a href="#tail" tabindex="3">Хвост</a> </li> <li> <a href="#mustache" tabindex="1">Усы</a> </li> <li> <a href="#paws" tabindex="2">Лапы</a> </li> </ul>
В примерах с ручным порядком навигации есть ещё одна ошибка — положительное значение tabindex
. Например когда элемент в конце страницы делают первым в порядке навигации. Из-за этого пользователя неожиданно переносит в конец страницы, а потом снова в начало.
<!-- Не делайте так ❌ --><header> <span> Блог о сыре </span> <nav> <ul> <li> <!-- При втором нажатии на Tab фокус попадёт сюда 2️⃣ --> <a href="/history/">Приключения сыров в истории</a> </li> <li> <!-- При третьем — сюда и так далее 3️⃣ --> <a href="/types/">Виды сыров</a> </li> <li> <a href="/degustation/">Дегустация</a> </li> </ul> </nav></header><footer> <form> <p>Подпишись на сырную рассылку, чтобы ничего не пропустить 🧀 </p> <label for="email">Ваша почта</label> <!-- При первом Tab фокус попадёт сюда 1️⃣ --> <input id="email" type="email" tabindex="1"> <button>Подписаться</button> </form></footer>
<!-- Не делайте так ❌ --> <header> <span> Блог о сыре </span> <nav> <ul> <li> <!-- При втором нажатии на Tab фокус попадёт сюда 2️⃣ --> <a href="/history/">Приключения сыров в истории</a> </li> <li> <!-- При третьем — сюда и так далее 3️⃣ --> <a href="/types/">Виды сыров</a> </li> <li> <a href="/degustation/">Дегустация</a> </li> </ul> </nav> </header> <footer> <form> <p>Подпишись на сырную рассылку, чтобы ничего не пропустить 🧀 </p> <label for="email">Ваша почта</label> <!-- При первом Tab фокус попадёт сюда 1️⃣ --> <input id="email" type="email" tabindex="1"> <button>Подписаться</button> </form> </footer>
Разработчики часто хотят помочь пользователям и предоставить им одинаковый опыт. К примеру, скринридеры могут перемещаться по заголовкам. Почему бы не добавить заголовки в порядок навигации и не сделать удобно всем? Это приведёт к противоположному результату.
Пользователи, которые видят интерфейс и используют клавиатуру, не ожидают, что могут сделать фокус на… тексте или картинке (кроме редких ситуаций). Это также будет странно и для пользователей скринридеров. Обычно они не ожидают, что у элемента с ролью heading
есть фокус.
<!-- Не делайте так ❌ --><h1 tabindex="0"> Пара слов обо мне</h1><h2 tabindex="0"> Моя сырная страсть</h2><h3 tabindex="0"> Как всё начиналось</h3>
<!-- Не делайте так ❌ --> <h1 tabindex="0"> Пара слов обо мне </h1> <h2 tabindex="0"> Моя сырная страсть </h2> <h3 tabindex="0"> Как всё начиналось </h3>
Другая разновидность ошибки с добавлением неинтерактивного элемента в порядок навигации — кастомные элементы, которые повторяют функциональность существующих HTML-тегов. Например, кастомные кнопки.
<!-- Не делайте так ❌ --><div role="button" tabindex="0">Притвориться кнопкой</div>
<!-- Не делайте так ❌ --> <div role="button" tabindex="0">Притвориться кнопкой</div>
Соберём всё самое плохое в одном месте. Попробуйте угадать, что в фокусе, а что нет, и в каком порядке.