Кратко
СкопированоГлобальный атрибут, который указывает браузеру, в каком порядке пользователь перемещается между элементами на странице. Может добавить фокус даже для неинтерактивных элементов — параграфов и заголовков.
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>
Соберём всё самое плохое в одном месте. Попробуйте угадать, что в фокусе, а что нет, и в каком порядке.