Фокус — ключевое состояние интерактивных элементов интерфейса. При навигации с клавиатуры пользователь перемещается между элементами управления: ссылками, кнопками, полями ввода, переключателями. В каждый момент времени только один элемент обладает состоянием фокуса.
Визуальный индикатор фокуса сообщает, какой элемент в данный момент активен для взаимодействия. Без такого индикатора пользователь клавиатуры теряет ориентацию внутри страницы. Это особенно критично для людей:
По стандартам доступности (WCAG) наличие видимого индикатора фокуса является обязательным требованием.
Во многих браузерах индикатор реализован по умолчанию — чаще всего
это контур (outline). Однако стандартное оформление не
всегда соответствует визуальному стилю интерфейса, поэтому разработчики
часто заменяют его собственными решениями. При неправильной реализации
это может привести к полной потере индикатора.
Библиотека ally.js предоставляет инструменты для управления поведением фокуса, включая безопасную кастомизацию его визуального отображения.
По умолчанию браузеры применяют CSS-правило:
:focus {
outline: auto;
}
В зависимости от браузера отображается:
Пример стандартного поведения:
<button>Сохранить</button>
При переходе к кнопке с клавиатуры появляется рамка.
Распространённая ошибка — полное отключение индикатора:
:focus {
outline: none;
}
Такое правило полностью удаляет визуальный сигнал фокуса. Пользователь клавиатуры больше не понимает, где находится курсор взаимодействия.
При кастомизации требуется соблюдать два принципа:
Для решения второй задачи ally.js предлагает механизм определения способа взаимодействия пользователя.
Пользователь, работающий мышью, не нуждается в постоянном отображении индикаторов фокуса. Например:
В современных интерфейсах принято:
Нативный CSS-псевдокласс :focus не умеет различать
источник взаимодействия. Для решения этой задачи в браузерах появился
псевдокласс :focus-visible, но его поддержка появилась
относительно недавно.
Библиотека ally.js реализует подобную логику независимо от поддержки браузера.
В ally.js существует модуль what-input и связанные механизмы определения того, каким способом пользователь взаимодействует со страницей.
Основная идея:
На основании этих данных можно управлять отображением индикатора фокуса.
Пример логики:
let usingKeyboard = false;
document.addEventListener('keydown', () => {
usingKeyboard = true;
});
document.addEventListener('mousedown', () => {
usingKeyboard = false;
});
Эта примитивная реализация показывает принцип, который ally.js реализует более корректно и устойчиво.
Один из инструментов библиотеки — управление стилями элементов, находящихся в состоянии фокуса или содержащих его внутри.
Модуль позволяет применять стили не только к самому элементу, но и к его контейнеру.
Пример:
import focusWithin from 'ally.js/style/focus-within';
const handle = focusWithin({
context: document.body
});
После активации модуля контейнер получает CSS-класс:
ally-focus-within
Этот класс можно использовать для стилизации интерфейса.
Пример CSS:
.ally-focus-within button:focus {
outline: 3px solid #005fcc;
}
Такой подход помогает создавать групповые индикаторы фокуса.
Индикатор может быть реализован различными способами:
outline)box-shadow)Наиболее гибким считается использование box-shadow.
button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(0, 120, 212, 0.6);
}
Преимущества:
В сложных интерфейсах бывает полезно выделять не только сам элемент, но и его родительский контейнер.
Пример: поле ввода внутри формы.
HTML:
<div class="form-field">
<label>Email</label>
<input type="email">
</div>
CSS:
.form-field.focused {
border-color: #2a7cff;
}
JavaScript с ally.js:
import observeFocus from 'ally.js/observe/focus';
observeFocus({
context: document.body,
callback(element) {
document
.querySelectorAll('.form-field')
.forEach(el => el.classList.remove('focused'));
const field = element.closest('.form-field');
if (field) {
field.classList.add('focused');
}
}
});
Теперь при фокусе на поле подсвечивается весь блок формы.
Современные интерфейсы часто используют кастомные компоненты:
Фокус может находиться внутри сложной структуры, поэтому важно корректно отображать активный элемент.
Пример компонента меню:
<ul class="menu">
<li><button>Файл</button></li>
<li><button>Правка</button></li>
<li><button>Вид</button></li>
</ul>
Кастомный индикатор:
.menu button:focus {
outline: none;
background: #e5f1ff;
}
С ally.js можно отслеживать текущий элемент и менять состояние родителя.
Модуль ally.js/observe/focus отслеживает изменения активного элемента.
Пример:
import observeFocus from 'ally.js/observe/focus';
const observer = observeFocus({
context: document.body,
callback: function(element) {
console.log('Focus moved to', element);
}
});
Это позволяет:
Кастомный индикатор может быть анимированным, что улучшает визуальную ориентацию.
Пример:
button {
transition: box-shadow 0.2s ease;
}
button:focus {
outline: none;
box-shadow: 0 0 0 4px rgba(80,150,255,0.5);
}
Эффект плавного появления облегчает восприятие перемещения фокуса.
Часто используется паттерн:
показывать фокус только при использовании клавиатуры
CSS:
.focus-visible {
box-shadow: 0 0 0 3px #2a7cff;
}
JavaScript с ally.js:
document.addEventListener('keydown', () => {
document.body.classList.add('keyboard-navigation');
});
document.addEventListener('mousedown', () => {
document.body.classList.remove('keyboard-navigation');
});
CSS:
.keyboard-navigation :focus {
outline: 3px solid #2a7cff;
}
Индикатор появляется только при клавиатурной навигации.
Модальные окна требуют особого контроля фокуса:
При использовании ally.js можно сочетать focus trap и кастомный индикатор.
CSS:
.modal :focus {
outline: none;
box-shadow: 0 0 0 3px #ff9800;
}
Фокус становится хорошо заметным внутри диалога.
Индикатор должен соответствовать требованиям контрастности.
Рекомендуемые параметры:
Пример универсального индикатора:
:focus {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
outline-offset помогает отделить рамку от элемента.
Иногда индикатор создается с помощью ::after.
Пример:
button {
position: relative;
}
button:focus::after {
content: "";
position: absolute;
inset: -4px;
border: 2px solid #2a7cff;
}
Это дает полный контроль над внешним видом индикатора.
:focus {
outline: none;
}
Без альтернативного индикатора это делает интерфейс недоступным.
:focus {
outline: 1px solid #ccc;
}
Такой индикатор практически не виден.
Некоторые интерфейсы показывают подсветку только при наведении мыши, но не при фокусе клавиатурой.
Иногда рамка скрывается из-за overflow: hidden.
Решение:
box-shadowoutline-offsetHTML:
<button class="primary">Отправить</button>
CSS:
.primary {
background: #2a7cff;
color: white;
border: none;
padding: 10px 16px;
}
.primary:focus {
outline: none;
box-shadow:
0 0 0 3px white,
0 0 0 6px #2a7cff;
}
Результат:
Библиотека ally.js предоставляет инструменты:
Кастомные индикаторы фокуса становятся особенно полезными при использовании:
В таких сценариях визуальная подсветка активного элемента играет ключевую роль в обеспечении доступности и удобства клавиатурной навигации.