Фокус клавиатуры является ключевым элементом доступности
веб-интерфейсов. Пользователи, которые перемещаются по странице при
помощи клавиатуры, экранных дикторов или вспомогательных устройств
ввода, ориентируются по визуальным индикаторам фокуса. Исторически
браузеры отображали фокус при помощи стандартной обводки
(outline), однако разработчики часто отключали её из
соображений дизайна.
Проблема возникает в ситуациях, когда визуальный индикатор фокуса удаляется полностью. В таком случае пользователи клавиатуры теряют ориентацию на странице.
Псевдокласс :focus-visible был создан для решения этой
проблемы. Он позволяет показывать визуальный индикатор фокуса только
тогда, когда это действительно необходимо — например, при навигации с
клавиатуры.
Пример базового применения:
button:focus-visible {
outline: 3px solid #3b82f6;
}
Браузер сам определяет, следует ли отображать индикатор фокуса. Если элемент получил фокус через клик мышью, индикатор может не отображаться. Если фокус получен через клавиатуру — индикатор отображается.
Несмотря на удобство механизма, поддержка :focus-visible
долгое время оставалась неполной. Для решения проблем совместимости
используются библиотеки и полифиллы, среди которых важную роль играет
ally.js.
Разные способы взаимодействия с интерфейсом требуют разных правил отображения фокуса.
Основные сценарии:
| Способ взаимодействия | Требование |
|---|---|
| Клавиатура | Фокус должен быть явно виден |
| Мышь | Индикатор может быть скрыт |
| Сенсорный ввод | Визуальный индикатор обычно не требуется |
Без механизма различения этих сценариев возникает одна из двух проблем:
Псевдокласс :focus-visible решает задачу на уровне CSS,
но для поддержки старых браузеров и сложных сценариев применяется
программная логика.
ally.js — специализированная библиотека для работы с доступностью и управлением фокусом в интерфейсах. Она предоставляет инструменты для:
Интеграция с :focus-visible позволяет дополнить нативную
поддержку браузеров и создать единый механизм управления визуальным
фокусом.
Для корректной работы :focus-visible необходимо
определять тип последнего пользовательского взаимодействия. ally.js
может отслеживать события ввода и переключать режим интерфейса.
Типичные события:
keydownmousedownpointerdowntouchstartПростейший механизм отслеживания:
let usingKeyboard = false;
document.addEventListener('keydown', () => {
usingKeyboard = true;
});
document.addEventListener('mousedown', () => {
usingKeyboard = false;
});
В зависимости от значения переменной интерфейс может менять стили фокуса.
Практической стратегией является добавление CSS-класса к корневому элементу документа.
Пример:
function handleKeyboard() {
document.documentElement.classList.add('user-is-tabbing');
}
function handleMouse() {
document.documentElement.classList.remove('user-is-tabbing');
}
document.addEventListener('keydown', handleKeyboard);
document.addEventListener('mousedown', handleMouse);
CSS:
:focus {
outline: none;
}
.user-is-tabbing :focus {
outline: 3px solid #2563eb;
}
Такой подход имитирует поведение :focus-visible.
Библиотека ally.js предоставляет более надёжные механизмы определения фокусируемых элементов и работы с фокусом.
Подключение библиотеки:
<script src="https://unpkg.com/ally.js/ally.min.js"></script>
Одним из полезных инструментов является определение фокусируемых элементов.
const elements = ally.query.focusable({
context: document
});
Результатом является список элементов, которые могут получить фокус с точки зрения доступности.
Это важно для корректной интеграции с логикой
:focus-visible, поскольку не все элементы с
tabindex действительно должны участвовать в навигации.
Программная установка фокуса может нарушать логику
:focus-visible, поскольку браузер не всегда знает, каким
способом был вызван фокус.
Пример:
button.focus();
Если фокус устанавливается программно после клика мышью, визуальный индикатор может появиться неожиданно.
ally.js помогает контролировать такие ситуации через управление последовательностью фокуса.
Пример использования:
ally.element.focus(targetElement);
Этот метод учитывает особенности браузеров и корректно обрабатывает фокусируемость элементов.
На практике рекомендуется комбинировать CSS-псевдокласс
:focus-visible и JavaScript-логику.
Пример:
:focus:not(:focus-visible) {
outline: none;
}
:focus-visible {
outline: 3px solid #2563eb;
}
Для браузеров без поддержки псевдокласса используется JavaScript-fallback.
if (!CSS.supports('selector(:focus-visible)')) {
document.documentElement.classList.add('no-focus-visible');
}
CSS-альтернатива:
.no-focus-visible .user-is-tabbing :focus {
outline: 3px solid #2563eb;
}
Особое внимание требуется при работе со следующими элементами:
В модальных диалогах используется ловушка фокуса. ally.js предоставляет API для создания таких механизмов.
const trap = ally.maintain.tabFocus({
context: dialogElement
});
Фокус не выходит за пределы диалога, что улучшает навигацию клавиатурой.
В сочетании с :focus-visible это обеспечивает корректное
отображение индикаторов внутри модального окна.
Иногда интерактивность добавляется элементам, которые изначально не поддерживают фокус.
Пример:
<div tabindex="0" role="button">
Открыть меню
</div>
ally.js позволяет проверить, соответствует ли элемент требованиям фокусируемости.
ally.is.focusable(element);
После этого можно безопасно применять стили
:focus-visible.
Компоненты навигации часто используют стрелочную навигацию.
При программном перемещении фокуса важно сохранять корректное отображение индикатора.
ally.element.focus(nextItem);
Это предотвращает потерю фокуса и обеспечивает совместимость со скринридерами.
Поддержка :focus-visible появилась в современных
версиях:
Однако в старых браузерах псевдокласс отсутствует.
Основные проблемы:
Комбинация ally.js и JavaScript-fallback позволяет устранить эти различия.
focus-visibleВ проектах часто используется официальный полифилл.
Принцип работы:
.focus-visibleПример:
import 'focus-visible';
CSS:
.focus-visible {
outline: 3px solid #2563eb;
}
В сочетании с ally.js полифилл становится более надёжным, поскольку библиотека обеспечивает корректную работу фокуса.
Распространённая структура решения:
:focus-visibleПоследовательность подключения:
focus-visible polyfill
↓
ally.js
↓
CSS focus styles
↓
JavaScript logic for input detection
Такая архитектура обеспечивает:
Визуальный индикатор должен быть:
Пример:
:focus-visible {
outline: 3px solid #1d4ed8;
outline-offset: 2px;
}
Использование outline-offset позволяет избежать
наложения на границы элементов.
Также распространён комбинированный вариант:
:focus-visible {
box-shadow: 0 0 0 3px rgba(59,130,246,0.6);
}
Это создаёт мягкий визуальный индикатор, совместимый с современными дизайн-системами.
После интеграции :focus-visible и ally.js необходимо
проверить:
TabТипичные инструменты тестирования:
Корректная интеграция позволяет сохранить баланс между визуальным дизайном и требованиями доступности, обеспечивая стабильную и предсказуемую работу фокуса во всех сценариях взаимодействия.