Клавиатурный доступ и фокус

Что это такое

Клавиатурный доступ — гарантия, что каждое действие, доступное мыши, достижимо и с помощью Tab, Enter, Space и стрелок, а пользователь всегда видит, где находится. Фокус — метка браузера для «элемента, который получит следующее нажатие клавиши», то есть курсор пользователя с клавиатуры и его чувство положения.

Почему это важно

Многие пользуются клавиатурой, потому что не могут пользоваться указывающим устройством, а многие другие переключаются на неё ради скорости. Если элемент управления недостижим по Tab, для них его не существует, и обходного пути нет. Фокус — более трудная половина: невидимое кольцо создаёт ту же проблему, что и полное его отсутствие, ведь пользователь печатает в то, что не может опознать. Обе ошибки невидимы разработчику, который проверяет мышью, — именно поэтому они и доживают до продакшена.

Ключевые идеи

Порядок табуляции повторяет DOM

Порядок табуляции — порядок документа, поэтому если визуальный порядок создан перестановкой через CSS — order во flex, размещением в grid, абсолютным позиционированием, — они расходятся. Пользователь с клавиатуры проходит страницу в последовательности, которая выглядит случайной: исправлять нужно разметку, а не добавлять положительные значения tabindex, которые ломают естественный порядок для всех последующих.

Никогда не убирайте кольцо фокуса, не заменив его

outline: none — самая распространённая ошибка доступности. Если кольцо по умолчанию конфликтует с дизайном, замените его более чётким, которое не опирается только на цвет.

:focus-visible {
  outline: 3px solid teal;
  outline-offset: 2px;
}

focus-visible показывает кольцо при работе с клавиатуры и обычно скрывает его при клике мышью — компромисс, которого хочет большинство интерфейсов.

Держите фокус там, где пользователь его видит

Всё, что перемещает внимание пользователя — открытие меню, открытие диалога, удаление строки, на которой он был, — должно намеренно переносить фокус и возвращать его, когда взаимодействие закончилось. Диалог, который открывается, не перемещая фокус, оставляет пользователя табулировать по странице за ним: это даже запутаннее, чем звучит.

dialog.showModal();               // перемещает фокус в диалог
closeButton.addEventListener("click", () => {
  dialog.close();
  trigger.focus();                // возвращает фокус туда, откуда он пришёл
});

Перехватывайте фокус только внутри модального окна и дайте выход

Пока модальный диалог открыт, Tab должен циклически проходить внутри него, а Escape — закрывать его. Везде в другом месте перехват фокуса — ошибка: он оставляет пользователя без выхода, а нативный элемент dialog с showModal() обрабатывает оба поведения — сильный довод в пользу его использования.

Дайте способ миновать повторяющуюся навигацию

Ссылка «Перейти к основному содержимому», видимая при фокусе, позволяет пользователю с клавиатуры миновать заголовок на каждой странице. Это один элемент, и он экономит несколько десятков переходов Tab при каждом визите.

Практика

Пройдите страницу по Tab и запишите каждую остановку

Откройте страницу, которую сопровождаете, и нажимайте Tab, пока не покинете страницу, отмечая каждый элемент и то, видно ли, где находится фокус.

Готово, когда: каждая остановка видима, ни один элемент управления не пропущен, а порядок совпадает с визуальным — и любое расхождение исправлено в разметке, а не через tabindex.

Соберите диалог, из которого можно выйти

Откройте диалог кнопкой, переместите в него фокус, пройдите Tab по кругу внутри него, закройте его клавишей Escape и кнопкой закрытия.

Готово, когда: при открытии фокус находится внутри диалога, не может его покинуть, пока диалог открыт, возвращается к кнопке-триггеру при закрытии, а Escape закрывает диалог из любого положения.

Частые ошибки

  • Удаление обводки фокуса — пользователи с клавиатуры теряют всякое чувство положения; исправить: переоформите :focus-visible вместо того, чтобы его удалять.
  • Положительный tabindex — элемент прыгает в начало порядка, и всё после него сдвигается; исправить: держите tabindex равным 0 или не задавайте его вовсе, а порядок правьте в разметке.
  • Открытие диалога без переноса фокуса — диалог выглядит модальным, но клавиатура остаётся за ним; исправить: переносите фокус при открытии и возвращайте его при закрытии.
  • Обработчик клика на неинтерактивном элементе — действие недостижимо без мыши; исправить: используйте button или ссылку, чтобы элемент участвовал в порядке табуляции.
  • :hover как единственный способ раскрытия — меню и подсказки, которым нужен указатель, никогда не открываются для пользователя с клавиатуры; исправить: открывайте их и по фокусу, а содержимое делайте закрываемым по Escape.

Что читать дальше

  • Семантика и ARIA (02-frontend/11-accessibility-wcag-a11y/01-semantics-and-aria) — роли и имена решают, что услышит пользователь с клавиатуры, когда фокус попадёт на элемент.
  • Инструменты разработчика в браузере (02-frontend/27-browser-devtools) — прогон с клавиатуры при открытом инспекторе доступности показывает ровно то, что объявляется.
  • Юзабилити-тестирование (03-design-ux/33-usability-testing) — наблюдение за одним пользователем с клавиатурой за минуты находит барьеры.