Как и когда использовать атрибут tabindex
Атрибут tabindex управляет получением фокуса и участием элемента в обходе по Tab. Значение 0 добавляет элемент в обычный порядок, а -1 оставляет программный фокус. Положительные значения почти всегда усложняют клавиатурную навигацию.
Вы разберетесь, как сделать нестандартный элемент доступным, перевести фокус в модальное окно и вернуть его после закрытия. В конце статьи есть чек-лист для ручной проверки и инструментов разработчика.
Как устроен порядок фокуса
Фокус получает элемент, которому браузер передает клавиатурный ввод.
Поле с фокусом принимает текст, а кнопка реагирует на активацию. Клавиша Tab перемещает фокус вперед, Shift+Tab возвращает его назад.
При обычной разметке порядок обхода соответствует расположению элементов в DOM.
Без дополнительного tabindex фокус обычно получают ссылки и элементы управления:
<a href>и<area href>– ссылки и активные области карты изображения;<button>,<select>,<textarea>и<input>, кромеtype="hidden";<iframe>– встроенный документ;- первый
<summary>внутри<details>– переключатель раскрываемого блока; <audio controls>и<video controls>– медиаплееры с элементами управления;- элементы с включенным
contenteditable– редактируемые области.
Список зависит от состояния элемента и браузера. Элемент формы с действующим disabled, скрытый элемент и содержимое области inert фокус не получают.
Тег <link> подключает ресурс и не заменяет видимую ссылку <a href>.
Программный фокус задает метод HTMLElement.focus():
document.querySelector('a[href]').focus();Обычный <div> сам по себе фокус не получает, но tabindex может добавить ему такую возможность. Это меняет только фокус, но не добавляет роли, имени или действия.
Как использовать tabindex
Значение атрибута должно быть целым числом: отрицательным, положительным или равным нулю. Для большинства интерфейсов достаточно -1 и 0.
Полное описание атрибута есть в документации MDN по tabindex.
| Поведение | tabindex=-1 | tabindex=0 | tabindex больше 0 |
|---|---|---|---|
| Участие в обходе по Tab | Нет | Да, на своем месте в DOM | Да, перед обычными элементами |
| Вызов focus() | Работает | Работает | Работает |
| Фокус при клике | Возможен* | Возможен* | Возможен* |
| Когда применять | Цель программного перехода | Точка входа в пользовательский виджет | Обычно не применять |
*Фокус при клике зависит от элемента, браузера и платформы. Таблица относится к отображаемым элементам без состояний вроде disabled и inert.
Отрицательные значения tabindex
Значение tabindex=-1 исключает элемент из последовательного обхода по Tab. Метод focus() при этом продолжает работать, а клик мышью тоже может передать элементу фокус.
<p id='notice' tabindex='-1'>Изменения сохранены.</p>
<button type='button' id='show-notice'>Перейти к сообщению</button>
<script>
document.querySelector('#show-notice').addEventListener('click', () => {
document.querySelector('#notice').focus();
});
</script>После нажатия кнопки скрипт переведет фокус на сообщение. При обычном обходе Tab браузер его пропустит, поэтому пользователь не потеряет привычный маршрут.
Для отрицательного значения используйте запись -1. Другие отрицательные числа не дают дополнительных преимуществ.
Не ставьте -1 на обычную кнопку без другого клавиатурного управления: пользователь потеряет доступ к ее действию.
Значение tabindex, равное нулю
Значение tabindex=0 включает элемент в обычный порядок обхода. Браузер учитывает положение элемента в DOM.
<button type='button'>Первый элемент</button>
<div tabindex='0'>Второй элемент</div>
<a href='/'>Третий элемент</a>Нажатия Tab последовательно выделят кнопку, контейнер и ссылку.
Атрибут не добавляет контейнеру роль или действие, поэтому интерактивному виджету нужны семантика, обработчики клавиш и видимый результат.
Положительные значения tabindex
Браузер сначала обходит элементы с положительным tabindex, начиная с меньшего числа. Затем идут обычные интерактивные элементы и элементы с нулевым значением.
<button type='button'>Третья остановка</button>
<button type='button' tabindex='2'>Вторая остановка</button>
<button type='button' tabindex='1'>Первая остановка</button>Положительный tabindex легко ломает маршрут при добавлении новых элементов. Визуальное расположение расходится с порядком фокуса, а ручная нумерация усложняет поддержку.
Критерий WCAG 2.4.3 Focus Order требует сохранять смысл и управляемость при последовательной навигации. Поэтому переставьте элементы в HTML, а не нумеруйте остановки.
Lighthouse и axe DevTools отмечают элементы со значением больше 0.
Программный фокус и tabindex
Метод focus() переводит фокус на конкретный элемент в момент вызова. Он работает с нативными интерактивными элементами без дополнительного tabindex.
Для обычного контейнера добавьте tabindex=-1, если остановка по Tab ему не нужна. Такой прием подходит для заголовка результатов или сообщения об ошибке.
<h2 id='results-title' tabindex='-1'>Результаты поиска</h2>
<script>
function focusResults() {
document.querySelector('#results-title').focus();
}
</script>Вызовите focusResults() после явного действия пользователя, например отправки поиска. Перед вызовом покажите результаты и уберите скрывающее состояние контейнера, чтобы браузер мог передать ему фокус.
Само объявление функции фокус не перемещает. Не переводите фокус при фоновой загрузке: такой переход прервет ввод текста и собьет пользователя.
Когда использовать tabindex
Выбирайте значение по задаче элемента в клавиатурном интерфейсе. Для программной цели подходит -1, для точки входа в пользовательский виджет – 0.
Обычным ссылкам и кнопкам дополнительный атрибут чаще всего не нужен. Нативные элементы уже дают семантику и клавиатурное управление.
Когда использовать отрицательное значение tabindex
Используйте -1, если скрипт должен перевести фокус на заголовок, сообщение или контейнер. Такой элемент не становится лишней остановкой при обычной табуляции, но остается доступным через focus().
Skip-link тоже использует этот прием. Укажите в ссылке идентификатор основного содержимого, а элементу <main> добавьте tabindex="-1".
<a class='skip-link' href='#main-content'>Перейти к содержимому</a>
<main id='main-content' tabindex='-1'>
<h1>Каталог</h1>
<a href='/catalog.html'>Открыть каталог</a>
</main>После активации ссылки клавишей Enter фокус перейдет на main. Следующий Tab продолжит маршрут с первой ссылки внутри основного содержимого.
Не скрывайте skip-link полностью: пользователь должен видеть ее при получении фокуса.
Как перевести фокус в модальное окно и вернуть обратно
Для модального окна используйте нативный элемент dialog с методом showModal(). Браузер делает остальную страницу неактивной и ограничивает клавиатурную навигацию содержимым окна.
Поставьте tabindex=-1 на заголовок, если хотите начать чтение с него. Самому элементу dialog этот атрибут не задавайте.
<button type='button' id='open-settings'>Открыть настройки</button>
<dialog id='settings' aria-labelledby='settings-title'>
<h2 id='settings-title' tabindex='-1'>Настройки уведомлений</h2>
<label>
<input type='checkbox'> Получать новости
</label>
<button type='button' id='close-settings'>Закрыть</button>
</dialog>
<script>
const openSettings = document.querySelector('#open-settings');
const closeSettings = document.querySelector('#close-settings');
const settings = document.querySelector('#settings');
const settingsTitle = document.querySelector('#settings-title');
let returnFocusTo = null;
openSettings.addEventListener('click', () => {
returnFocusTo = openSettings;
settings.showModal();
settingsTitle.focus();
});
closeSettings.addEventListener('click', () => settings.close());
settings.addEventListener('close', () => {
if (returnFocusTo?.isConnected) returnFocusTo.focus();
});
</script>После открытия фокус перейдет на заголовок окна. Tab и Shift+Tab останутся внутри модального окна, а клавиша Escape или кнопка «Закрыть» завершит его работу.
Событие close возвращает фокус на кнопку открытия. Если окно закрыли другим способом, эта обработка сохраняет тот же маршрут.
Собственная реализация на основе div требует доступного имени, обработки Escape, циклического обхода и блокировки фона.
Для фоновых контейнеров используйте атрибут inert, а перед возвратом фокуса снимите его с фона.
Когда использовать tabindex=0
Используйте tabindex=0, когда пользовательский интерактивный элемент должен получать фокус по Tab. Для обычной кнопки выбирайте нативный <button>: он уже поддерживает роль и активацию клавишами.
<button type='button'>Показать сообщение</button>Если проект требует пользовательский элемент, добавьте роль button, имя и обработчики Enter и Space. Оба нажатия должны запускать то же действие, что и клик.
<my-custom-button id='notify' role='button' tabindex='0'>Показать сообщение</my-custom-button>
<p id='status' role='status'></p>
<script>
const notify = document.querySelector('#notify');
const status = document.querySelector('#status');
let count = 0;
function activate() {
count += 1;
status.textContent = `Сообщение показано. Нажатий: ${count}`;
}
notify.addEventListener('click', activate);
notify.addEventListener('keydown', (event) => {
if (event.key === ' ') event.preventDefault();
if (event.key === 'Enter' && !event.repeat) {
event.preventDefault();
activate();
}
});
notify.addEventListener('keyup', (event) => {
if (event.key === ' ') {
event.preventDefault();
activate();
}
});
</script>Enter запускает действие при нажатии, Space – при отпускании. Отмена стандартного поведения Space предотвращает прокрутку страницы, а роль button сообщает назначение элемента вспомогательным технологиям.
Добавьте заметный индикатор фокуса:
:focus-visible {
outline: 3px solid #0759c7;
outline-offset: 3px;
}Псевдокласс :focus-visible показывает индикатор, когда браузер считает его нужным. Не удаляйте стандартный outline без равноценной замены.
Когда использовать положительное значение tabindex
Не используйте положительный tabindex для исправления порядка обычной страницы. Переставьте элементы в HTML так, чтобы смысловой маршрут совпадал с визуальным.
CSS-свойства order и размещение в сетке сами порядок DOM не исправляют. Искусственная нумерация ломается при добавлении новых элементов, поэтому поддерживать такой маршрут трудно.
Положительные значения могут расходиться с визуальным порядком и нарушать критерий WCAG 2.4.3 Focus Order. Lighthouse и axe DevTools отмечают элементы со значением больше 0.
const positiveTabindex = [...document.querySelectorAll('[tabindex]')]
.filter((element) => element.tabIndex > 0);
console.log(positiveTabindex);Консоль выведет элементы текущего документа с положительным значением атрибута. Найдите их в HTML и уберите нумерацию.
- Сохраните копию разметки перед изменением маршрута.
- Удалите положительные значения у нативных ссылок, кнопок и полей.
- Замените положительные значения пользовательских виджетов на
0, если им нужна остановка по Tab. - Переставьте элементы в DOM согласно порядку чтения и выполнения задачи.
- Пройдите страницу через Tab и Shift+Tab в обоих направлениях.
Если изменение сломало интерфейс, восстановите сохраненную разметку. Затем исправьте структуру и связанные CSS-правила вместе.
Как применять roving tabindex в составных виджетах
В панели инструментов, меню или списке вкладок много связанных элементов. Пользователь должен входить в такой виджет одним нажатием Tab, а стрелки должны перемещать фокус внутри него.
Прием roving tabindex оставляет 0 у одного пункта. Остальные получают -1, а стрелки переводят фокус через focus().
<div id='alignment' role='toolbar' aria-label='Выравнивание текста'>
<button type='button' tabindex='0'>Влево</button>
<button type='button' tabindex='-1'>По центру</button>
<button type='button' tabindex='-1'>Вправо</button>
</div>
<script>
const toolbar = document.querySelector('#alignment');
const items = [...toolbar.querySelectorAll('button')];
function setEntry(target) {
items.forEach((item) => item.tabIndex = item === target ? 0 : -1);
}
toolbar.addEventListener('keydown', (event) => {
const index = items.indexOf(document.activeElement);
if (index === -1) return;
let next = index;
if (event.key === 'ArrowRight') next = (index + 1) % items.length;
else if (event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length;
else if (event.key === 'Home') next = 0;
else if (event.key === 'End') next = items.length - 1;
else return;
event.preventDefault();
setEntry(items[next]);
items[next].focus();
});
</script>Tab вводит фокус в панель один раз. Стрелки перемещают его между кнопками, а следующий Tab покидает панель.
Для вкладок и меню адаптируйте роли, состояния и правила активации по шаблонам ARIA Authoring Practices Guide.
Как проверить порядок фокуса на странице
Начните с ручного обхода без мыши. Автоматические инструменты находят часть ошибок, но только вы проверите смысл маршрута и удобство перемещения.
- Перезагрузите страницу и нажимайте Tab. Каждая остановка должна иметь видимый индикатор и понятное назначение.
- Пройдите тот же маршрут через Shift+Tab. Фокус не должен неожиданно перескакивать между несвязанными областями.
- Активируйте кнопки через Enter и Space, а ссылки – через Enter.
- Откройте модальное окно и проверьте начальную позицию фокуса, фон и возврат после закрытия.
- Активируйте skip-link. Следующий Tab должен продолжать маршрут в основном содержимом.
- Проверьте составные виджеты стрелками. Tab должен входить в каждый виджет через одну точку.
В инструментах разработчика Chrome откройте область Accessibility у выбранного элемента. Она показывает дерево доступности, ARIA-атрибуты и вычисляемые свойства. Выражение document.activeElement возвращает текущий элемент с фокусом.
document.activeElementСопоставьте вывод консоли с подсветкой на странице. Так вы увидите, куда ушел фокус после каждого нажатия.
Запустите аудит доступности Lighthouse и повторите ручной обход после исправлений. Lighthouse помогает найти положительные значения tabindex, но не заменяет проверку маршрута клавишами.
Чек-лист выбора tabindex и доступности
- Обычные ссылки и кнопки используют нативные HTML-элементы без лишнего
tabindex. - Цели программного перехода получают
-1, если остановка по Tab им не нужна. - Пользовательские элементы с
0имеют роль, имя и клавиатурное управление. - Положительных значений нет, а порядок DOM сохраняет смысл страницы.
- Фокус виден и не скрывается под перекрывающими блоками.
- Модальное окно блокирует фон и возвращает фокус после закрытия.
- Составной виджет имеет одну точку входа с
0. - Skip-link переводит фокус на
mainсtabindex=-1. - Обход Tab и Shift+Tab сохраняет ожидаемый порядок.
Совет от нашей редакции: сначала постройте логичный маршрут нативными HTML-элементами. Добавляйте tabindex только для конкретной задачи управления фокусом.
Популярное за неделю
- Как создать сайт самому в 2026: 4 способа + пошаговая инструкция 34 мин чтения
- Лучшие конструкторы для создания сайтов — плюсы, минусы и сравнения 22 мин чтения
- Форма авторизации на HTML и CSS: код входа и регистрации 8 мин чтения
- Как сделать отступ в HTML: табуляция и margin 3 мин чтения
- Как посмотреть исходный HTML-код страницы в браузере 4 мин чтения
- Как редактировать html файл в Chrome 1 мин чтения
Комментарии