Атрибут tabindex управляет тем, попадает ли элемент в последовательный переход по клавише Tab, и в каком порядке. Это глобальный атрибут: его можно прописать на любом теге, включая те, что сами по себе с клавиатуры не фокусируются.
<div tabindex="0">Теперь сюда можно перейти по Tab</div>
<div>А сюда - нельзя, это обычный div</div>
В этой статье
- Атрибут tabindex в HTML: что это и какие значения допустимы
- tabindex="-1": фокус по коду, но не по Tab
- Почему tabindex больше нуля - плохая практика
- div с tabindex не становится кнопкой для скринридера
- Паттерн "подвижный tabindex" для групп виджетов
- Частые ошибки с атрибутом tabindex
- Шпаргалка по атрибуту tabindex
Атрибут tabindex в HTML: что это и какие значения допустимы
Значение tabindex - целое число, и от его знака зависит поведение:
| Значение | Что происходит |
|---|---|
| Отрицательное (обычно -1) | Элемент можно сфокусировать из кода (element.focus()) или кликом, но по Tab до него не дойти |
| 0 | Элемент включается в Tab-переход, место в очереди - по порядку в разметке |
| Положительное (1-32767) | Элемент получает фокус раньше всех нулевых и отрицательных, порядок - по возрастанию числа |
| Атрибут не указан | Браузер сам решает, фокусируется элемент или нет - зависит от тега |
У части тегов переход по Tab уже работает без всякого tabindex - у ссылки с href, у <button>, <input>, <select> и <textarea>. Прописывать им tabindex="0" незачем - он там и так есть по умолчанию. Исключение - <dialog>: на нём tabindex использовать не стоит вовсе, спецификация это отдельно оговаривает.
tabindex="-1": фокус по коду, но не по Tab
tabindex="-1" убирает элемент из очереди Tab, но не из фокуса вообще - на него всё ещё можно перевести фокус кодом через .focus() или кликом мышью. Типичный случай - сообщение об ошибке формы: пользователь не должен натыкаться на него клавишей Tab в обычной навигации, но после неудачной отправки формы фокус нужно сразу перевести на него, чтобы скринридер прочитал текст ошибки.
Пример: фокус на ошибке без участия в Tab-очереди
<p id="err" tabindex="-1">Заполните поле email</p>
if (!field.value) {
err.hidden = false;
err.focus();
}
После отправки пустой формы фокус кодом переводится на сообщение об ошибке - а следующий Tab пропускает его и уходит дальше по форме.
Почему tabindex больше нуля - плохая практика
Положительный tabindex выглядит как способ задать свой порядок перехода, но на практике ломает его: такие элементы получают фокус раньше всех остальных, причём по значению числа, а не по месту в разметке. Из-за этого порядок Tab перестаёт совпадать с тем, что видно на экране, и для человека, который ориентируется только по клавиатуре, страница превращается в головоломку.
Пример: положительный tabindex переставляет порядок
<button tabindex="3">Кнопка A</button>
<button tabindex="1">Кнопка B</button>
<button tabindex="0">Кнопка C</button>
<button tabindex="2">Кнопка D</button>
Кнопки идут в разметке A, B, C, D, но реальный порядок Tab - по возрастанию положительных значений, а нулевые уходят в самый конец.
Поэтому в руководствах по доступности положительные значения не рекомендуют вообще - для корректного порядка проще располагать элементы в разметке в том порядке, в котором они должны идти по Tab, и использовать только 0 и -1.
div с tabindex не становится кнопкой для скринридера
tabindex даёт элементу фокус, но не роль и не поведение. <div tabindex="0"> с обработчиком клика визуально ведёт себя как кнопка и получает фокус по Tab, но для скринридера это по-прежнему обычный безымянный блок, а не кнопка: он не объявит ни роль «кнопка», ни то, что элемент можно нажать пробелом или Enter - это поведение у <button> встроено, а у div его никто не добавляет автоматически.
<!-- так видит скринридер: безымянный блок -->
<div tabindex="0" onclick="save()">Сохранить</div>
<!-- так правильно: роль, поведение клавиатуры и фокус - всё уже есть -->
<button type="button" onclick="save()">Сохранить</button>
Если элемент должен вести себя как кнопка, ссылка или поле ввода, проще и надёжнее взять готовый тег с нужной семантикой, чем добавлять tabindex к div и достраивать роль и клавиатурные обработчики вручную.
Паттерн "подвижный tabindex" для групп виджетов
Когда на странице есть группа связанных элементов - например, переключатель размера текста или панель инструментов - Tab не должен останавливаться на каждом из них по отдельности: пользователю достаточно одной остановки на всей группе, а дальше переключаться стрелками. Для этого применяют паттерн roving tabindex («подвижный tabindex»): tabindex="0" есть только у одного, активного элемента группы, у всех остальных - tabindex="-1". При переключении стрелками значение tabindex="0" переставляется на новый активный элемент, а старый получает tabindex="-1".
Пример: переключение внутри группы без лишних остановок Tab
<div class="toolbar">
<button tabindex="0">Маленький</button>
<button tabindex="-1">Средний</button>
<button tabindex="-1">Крупный</button>
</div>
// при переходе стрелкой на новую кнопку:
старыйАктив.tabIndex = -1;
новыйАктив.tabIndex = 0;
новыйАктив.focus();
Стрелками переключается активная кнопка внутри группы, а Tab заходит в группу один раз и сразу выходит за неё, к следующему элементу страницы.
Частые ошибки с атрибутом tabindex
С tabindex чаще путают не синтаксис, а то, что именно он даёт элементу и чего не даёт.
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| tabindex="0" прописывают ссылке, кнопке или полю ввода | Эти теги и так в Tab-очереди по умолчанию - лишний tabindex ничего не меняет, только засоряет разметку | Добавлять tabindex только тем тегам, которые сами по себе не фокусируются |
| div с tabindex считают полноценной заменой button | Скринридер не объявляет роль и не добавляет управление пробелом/Enter - это должен делать разработчик вручную, а у button оно уже есть | Использовать нативный <button> вместо div с tabindex, если нужна именно кнопка |
| Положительными значениями пытаются выстроить "правильный" порядок | Элементы с положительным tabindex выбиваются вперёд всех остальных по числу, а не по месту в разметке - порядок на экране и порядок Tab расходятся | Располагать элементы в разметке в нужном порядке и использовать только 0 и -1 |
| tabindex="-1" считают способом вообще убрать элемент из фокуса | Элемент всё ещё можно сфокусировать кодом или кликом - из Tab-очереди убирается только клавиатурный переход по порядку | Для полного исключения из фокуса нужны другие средства (disabled у полей форм, например) |
| Каждой кнопке тулбара прописывают свой tabindex="0" | Tab останавливается на каждой кнопке по отдельности, хотя пользователю удобнее одна остановка и переключение стрелками | Применять паттерн roving tabindex - tabindex="0" только у активного элемента группы |
Шпаргалка по атрибуту tabindex
| Правило | Что запомнить |
|---|---|
| Где работает | На любом теге (глобальный атрибут) |
| Отрицательное значение | Фокус только кодом/кликом, вне Tab-очереди |
| 0 | В Tab-очереди, порядок - по месту в разметке |
| Положительное значение | В Tab-очереди раньше всех, порядок - по возрастанию числа |
| Рекомендация | Использовать только 0 и -1, положительных избегать |
| a[href]/button/input/select/textarea | И так в Tab-очереди без tabindex |
<dialog> | tabindex на нём не используют |
| div/span с tabindex | Получает фокус, но не роль и не клавиатурное поведение кнопки |
| Roving tabindex | tabindex="0" у одного активного элемента группы, у остальных -1; переключение - стрелками |