Множество вспомогательных технологий используют навигацию с клавиатуры для помощи в восприятии и взаимодействии с контентом. Один из способов подобной навигации — клавиша Tab. Возможно, вы знакомы с ним, если используете Tab для быстрого перемещения между полями формы, не дотягиваясь до мышки или трекпада.
Tab будет перемещаться по интерактивным элементам в том порядке, в котором они отображаются в DOM. Вот почему так важно, чтобы порядок исходного кода соответствовал визуальной иерархии вашего дизайна.
Список интерактивных элементов, по которым можно пройтись клавишей Tab:
- Ссылки с заполненным атрибутом
href
; <button>
;<input>
и<textarea>
с сопутствующим им<label>
;<select>
;<details>
;<audio>
и<video>
при наличии контролов;<object>
, в зависимости от того, как он используется;- любой элемент с
overflow: scroll
в Firefox; - любой элемент с атрибутом
contenteditable
; - любой элемент с установленным атрибутом
tabindex
(о нём чуть позже).
Интерактивный элемент получает состояние фокуса, когда:
- На него переходят с помощью клавиши Tab,
- Он является ссылкой и на него кликают,
- Фокус программно задан с помощью
element.focus()
в JavaScript.
Фокус похож на ховер, поскольку так мы определяем элемент, с которым хотим провзаимодействовать. Вот почему визуально очевидные стили для фокуса имеют огромное значение.
Управление фокусом Скопировать ссылку
Управлять фокусом — значит определять, какие элементы могут его получать, а какие нет. Это один из самых сложных аспектов при разработке веб-интерфейсов, но он важен для доступности сайтов и приложений.
Полезные практики управления фокусом Скопировать ссылку
В 99% случаев вам стоит оставить порядок фокуса в покое. Не устану это повторять.
Состояние фокуса будет работать без дополнительных усилий, если вы используете <button>
для кнопок, <a>
для ссылок, <input>
для полей форм и т. д.
В редких случаях вам может понадобиться добавить фокус к элементам, к которым обычно событие focus
не применимо. Вот некоторые рекомендации относительно того, как реализовать это доступно и интуитивно понятно для пользователя.
✅ Следует: узнать побольше про атрибут tabindex
Скопировать ссылку
tabindex
делает элементы фокусируемыми. В качестве значения он принимает число, в зависимости от которого меняется поведение фокуса.
❌ Не следует: устанавливать tabindex="0"
там, где это не надо Скопировать ссылку
Нет необходимости устанавливать tabindex
для интерактивных элементов, которые могут получать фокус с клавиатуры (например, <button>
). Кроме того, вам не нужно прописывать tabindex
неинтерактивным элементам, чтобы их могли прочесть вспомогательные устройства (на самом деле, отсутствие роли и доступного имени является ошибкой с точки зрения WCAG). На самом деле, это даже усложняет навигацию для тех, кто использует вспомогательные устройства. У таких пользователей уже есть другие, ожидаемые ими способы чтения контента.
✅ Следует: устанавливать tabindex="-1"
для фокуса с помощью JavaScript Скопировать ссылку
Атрибут tabindex="-1"
используется для создания доступных интерактивных виджетов посредством JS. Прописав tabindex="-1"
, вы сделаете элемент фокусируемым для JS, а также по клику или тапу, но недоступным через клавишу Tab.
❌ Не следует: использовать положительное значение tabindex
Скопировать ссылку
Это антипаттерн. Установив положительное значение tabindex
, вы переопределите ожидаемый порядок элементов для фокуса через Tab и запутаете пользователя. Сделать так один раз — уже плохо, несколько — полный кошмар. Серьёзно, не надо так.
❌ Не следует: создавать собственный порядок фокусировки Скопировать ссылку
Пройтись по интерактивным элементам можно только в том порядке, в котором они следуют друг за другом. Не следует создавать множество tabindex
со значением, которое увеличивается для каждого последующего элемента, в соответствии с вашим представлением о том, как пользователь должен читать ваш сайт. Позвольте DOM сделать это за вас.
Ловушка фокуса Скопировать ссылку
Иногда есть необходимость запретить состояние фокуса. Хороший пример запрета — это ловушка фокуса, которая временно ограничивает событие фокуса у родительского элемента и у его дочерних элементов.
Ловушку фокуса не стоит путать с ловушкой клавиатуры. Ловушки клавиатуры — это ситуации, когда невозможно закрыть виджет или перейти к другому компоненту из-за вложенного цикла плохо прописанной логики.
Практический пример того, как вы могли бы использовать ловушку фокуса — это модальное окно:
Почему это важно? Скопировать ссылку
Удержание фокуса в пределах модального окна помогает понять, что является его контентом, а что нет. Это аналогично тому, как зрячий человек может видеть, как окно всплывает поверх контента сайта или приложения. Это важная информация, если:
- У вас плохое зрение или слепота и вы полагаетесь на скринридер, чтобы узнать об изменениях после взаимодействия со страницей;
- У вас плохое зрение и интерфейс увеличен с помощью экранной лупы, при которой фокусировка за пределами модального окна может сбить с толку и дезориентировать;
- Вы перемещаетесь исключительно с помощью клавиатуры и, в случае закрытия модального окна, можете потеряться на странице при попытке вернуться обратно к нему.
Как это сделать? Скопировать ссылку
Надёжно управлять фокусом — дело сложное. Нужно прибегнуть к JavaScript, чтобы:
- Определить родительский блок для всех фокусируемых элементов на странице;
- Определить границы содержимого ловушки фокуса (например, модального окна), включая первый и последний фокусируемый элемент;
- Убрать как интерактивность, так и видимость всего, что может иметь фокус и находится вне рамок содержимого ловушки фокуса;
- Переместить фокус на содержимое ловушки фокуса;
- Обрабатывать события, сигнализирующие об уходе с выделенной области (сохранение, отмена, нажатие Esc и так далее);
- Выйти из содержимого ловушки фокуса, когда сработает нужное событие;
- Вернуть раннее отменённую интерактивность;
- Переместить фокус обратно на интерактивный элемент, который вызвал ловушку фокуса.
Зачем нам это? Скопировать ссылку
Не стану врать: все эти действия отнимают много времени. Но всё же, управление фокусом и удобный порядок фокусировки являются частью WCAG (руководства по обеспечению доступности веб-контента). Тема достаточна важна, чтобы считать её частью международного правового стандарта о юзабилити.
Видимость Скопировать ссылку
Существует небольшой трюк, с помощью которого можно легко ограничить видимость и интерактивность элемента.
У скринридеров есть режим взаимодействия, который позволяет им проходить по странице или просматривать её с помощью виртуального курсора. А ещё виртуальный курсор позволяет пользователю скринридера обнаруживать неинтерактивные части страницы (заголовки, списки и т. д.). В отличие от использования Tab, виртуальный курсор доступен только пользователям скринридера.
При управлении фокусом может потребоваться ограничить возможность обнаружения содержимого виртуальным курсором. В нашем примере с модальным окном это значит предотвратить случайный выход пользователя за рамки окна при чтении контента.
Видимость может быть заблокирована с помощью правильного применения атрибута aria-hidden="true"
. А вот с интерактивностью есть один тонкий нюанс.
Знакомство с inert
Скопировать ссылку
Атрибут inert
— глобальный атрибут в HTML, позволяющий легко убирать и восстанавливать видимость интерактивных элементов, а также их возможность получать состояние фокуса. Вот пример:
<body>
<div
aria-labelledby="modal-title"
class="c-modal"
id="modal"
role="dialog"
tabindex="-1">
<div role="document">
<h2 id="modal-title">Save changes?</h2>
<p>The changes you have made will be lost if you do not save them.<p>
<button type="button">Save</button>
<button type="button">Discard</button>
</div>
</div>
<main inert>
<!-- ... -->
</main>
</body>
Я намеренно избегаю использования <dialog>
для модального окна из-за многочисленных проблем с поддержкой.
Атрибут inert
задан элементу <main>
, следующему сразу после модального окна сохранения изменений. Теперь всё содержимое <main>
не может попасть в фокус и по нему нельзя кликнуть.
Фокус ограничен рамками модального окна. Когда мы закрываем его, то убираем inert
из <main>
. Этот способ управления ловушкой фокуса проще, чем другие методы.
Помните: событие закрытия модального окна может быть вызвано не только нажатием на кнопки внутри нашего модального окна из примера, но и при нажатии на Esc. А ещё некоторые модальные окна могут быть закрыты по клику на оверлей.
Поддержка inert
Скопировать ссылку
Все последние версии Edge, Chrome и Opera поддерживают inert
, если включены экспериментальные функции веб-платформ. Также скоро появится его поддержка в Firefox! Единственное исключение — Safari, как мобильный, так и десктопный.
Мне бы очень хотелось, чтобы Apple реализовала встроенную поддержку inert
. Хоть полифил и существует, но у него серьёзные проблемы с поддержкой скринридерами. Нехорошо!
Вдобавок хочется обратить внимание на пометку в README полифила:
Полифил обойдётся вам дорого с точки зрения производительности, в отличие от нативного
inert
, т. к. он требует изрядного количества перемещений по DOM.
Перемещение по DOM подразумевает, что JavaScript в полифиле требует высокой вычислительной мощности и, следовательно, в конечном итоге замедлит использование.
Для устройств с низким энергопотреблением, таких как бюджетные Android-смартфоны и устаревшие ноутбуки, а также тех, что выполняют сложные задачи (например, запуск нескольких приложений сразу), это может привести к зависанию или сбоям. Нативная браузерная поддержка делает такие процессы менее затратными в этом плане, так как у браузера есть доступ ко всем частям DOM, в отличие от JS.
Safari Скопировать ссылку
Лично я разочарован тем, что Apple не поддерживает inert
. Хотя понимаю, что добавление новых функций в браузер — невероятно сложная и трудная работа. inert
кажется функцией, которую Apple стоило бы начать поддерживать гораздо раньше.
macOS и iOS всегда имели хорошую поддержку доступности, а поддержка вспомогательных технологий — важная часть их маркетинговой кампании. Поддержка inert
представляется как естественное продолжение миссии Apple ввиду того, что сама эта функция смогла бы облегчить разработку доступных веб-интерфейсов в разы.
К сожалению, Apple держит в тайне, когда появится поддержка этого атрибута. Поэтому поддержка inert
— всё ещё открытый вопрос.
Igalia Скопировать ссылку
Igalia — компания, работающая над функциями браузеров. Сейчас они проводят эксперимент, в котором каждый может проголосовать за те возможности браузеров, которые ему хотелось бы видеть. Конечно, моя статья совсем не про это, но вы можете узнать больше в Smashing Magazine.
Одно из предложений Igalia — это добавление поддержки inert
в WebKit. Если вы искали возможность принять участие в улучшении доступности веба, но не знали с чего начать, я могу вам её предоставить. 5 $, 10 $, 25 $ — совсем необязательно жертвовать большие суммы, даже маленький вклад ценен.
Итог Скопировать ссылку
Управление фокусом требует некоторых навыков и осторожности, но это того стоит. Атрибут inert
значительно облегчит эту задачу.
Такие техники, как inert
, являются примером одной из самых сильных сторон веба: способности кодифицировать неожиданное поведение в нечто простое и эффективное.
Что почитать Скопировать ссылку
- Controlling focus with tabindex (A11ycasts, эпизод 4).
- Using the tabindex attribute (The Paciello Group).
- Using JavaScript to trap focus in an element (Хидде де Врис).
Спасибо Адриану Розелли и Саре Хайли за их отзывы.