Интеграция с :focus-visible

Фокус клавиатуры является ключевым элементом доступности веб-интерфейсов. Пользователи, которые перемещаются по странице при помощи клавиатуры, экранных дикторов или вспомогательных устройств ввода, ориентируются по визуальным индикаторам фокуса. Исторически браузеры отображали фокус при помощи стандартной обводки (outline), однако разработчики часто отключали её из соображений дизайна.

Проблема возникает в ситуациях, когда визуальный индикатор фокуса удаляется полностью. В таком случае пользователи клавиатуры теряют ориентацию на странице.

Псевдокласс :focus-visible был создан для решения этой проблемы. Он позволяет показывать визуальный индикатор фокуса только тогда, когда это действительно необходимо — например, при навигации с клавиатуры.

Пример базового применения:

button:focus-visible {
  outline: 3px solid #3b82f6;
}

Браузер сам определяет, следует ли отображать индикатор фокуса. Если элемент получил фокус через клик мышью, индикатор может не отображаться. Если фокус получен через клавиатуру — индикатор отображается.

Несмотря на удобство механизма, поддержка :focus-visible долгое время оставалась неполной. Для решения проблем совместимости используются библиотеки и полифиллы, среди которых важную роль играет ally.js.


Проблема различия фокуса мыши и клавиатуры

Разные способы взаимодействия с интерфейсом требуют разных правил отображения фокуса.

Основные сценарии:

Способ взаимодействия Требование
Клавиатура Фокус должен быть явно виден
Мышь Индикатор может быть скрыт
Сенсорный ввод Визуальный индикатор обычно не требуется

Без механизма различения этих сценариев возникает одна из двух проблем:

  1. Индикатор отображается всегда и нарушает дизайн.
  2. Индикатор полностью отключается, что делает интерфейс недоступным.

Псевдокласс :focus-visible решает задачу на уровне CSS, но для поддержки старых браузеров и сложных сценариев применяется программная логика.


Библиотека ally.js и управление фокусом

ally.js — специализированная библиотека для работы с доступностью и управлением фокусом в интерфейсах. Она предоставляет инструменты для:

  • определения фокусируемых элементов
  • управления перемещением фокуса
  • обработки ловушек фокуса
  • работы с состояниями доступности

Интеграция с :focus-visible позволяет дополнить нативную поддержку браузеров и создать единый механизм управления визуальным фокусом.


Определение режима ввода

Для корректной работы :focus-visible необходимо определять тип последнего пользовательского взаимодействия. ally.js может отслеживать события ввода и переключать режим интерфейса.

Типичные события:

  • keydown
  • mousedown
  • pointerdown
  • touchstart

Простейший механизм отслеживания:

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

Библиотека 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 и JavaScript логики

На практике рекомендуется комбинировать 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 появилась в современных версиях:

  • Chrome
  • Firefox
  • Edge
  • Safari (новые версии)

Однако в старых браузерах псевдокласс отсутствует.

Основные проблемы:

  1. отсутствие псевдокласса
  2. различная логика отображения фокуса
  3. нестабильность при программной установке фокуса

Комбинация ally.js и JavaScript-fallback позволяет устранить эти различия.


Полифилл focus-visible

В проектах часто используется официальный полифилл.

Принцип работы:

  1. отслеживание событий клавиатуры
  2. добавление класса .focus-visible
  3. применение CSS-стилей

Пример:

import 'focus-visible';

CSS:

.focus-visible {
  outline: 3px solid #2563eb;
}

В сочетании с ally.js полифилл становится более надёжным, поскольку библиотека обеспечивает корректную работу фокуса.


Практическая архитектура реализации

Распространённая структура решения:

  1. Нативный :focus-visible
  2. Полифилл для старых браузеров
  3. ally.js для управления фокусом
  4. CSS-стили для визуального индикатора

Последовательность подключения:

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 необходимо проверить:

  1. навигацию клавишей Tab
  2. перемещение внутри модальных окон
  3. программные переходы фокуса
  4. взаимодействие со скринридерами

Типичные инструменты тестирования:

  • клавиатурная навигация без мыши
  • инспекторы доступности браузеров
  • автоматические аудит-инструменты

Корректная интеграция позволяет сохранить баланс между визуальным дизайном и требованиями доступности, обеспечивая стабильную и предсказуемую работу фокуса во всех сценариях взаимодействия.