Современные пользовательские интерфейсы состоят из множества переиспользуемых компонентов: кнопок, выпадающих списков, модальных окон, вкладок, меню, таблиц и других интерактивных элементов. При разработке таких компонентов важно учитывать не только визуальную часть и бизнес-логику, но и доступность для пользователей, использующих вспомогательные технологии — экранные дикторы, клавиатурную навигацию, альтернативные устройства ввода.
В HTML часть доступности уже обеспечивается нативными элементами
(button, input, select,
a и др.). Однако при создании кастомных компонентов на
основе div и span разработчик обязан
самостоятельно воспроизводить семантику и поведение элементов. Именно
здесь используются атрибуты ARIA (Accessible Rich Internet
Applications).
Библиотека aria-query предоставляет программный доступ к спецификации ARIA: ролям, свойствам, состояниям и связям между ними. Это позволяет создавать инструменты анализа и генерации доступной разметки, проверять корректность использования ролей и строить компоненты, соответствующие стандартам доступности.
aria-query — это библиотека, содержащая структурированное представление спецификации ARIA. Она включает:
Библиотека используется многими инструментами проверки доступности, включая линтеры и анализаторы DOM.
Основные экспортируемые структуры:
rolesariaelementRolesroleElementsdomКаждая из них представляет собой коллекции данных, описывающих правила доступности.
Установка выполняется через пакетный менеджер:
npm install aria-query
Импорт в проекте:
import { roles, elementRoles, roleElements } from "aria-query";
Библиотека не взаимодействует напрямую с DOM. Она предоставляет только справочные данные.
Коллекция roles содержит информацию о каждой роли
ARIA.
Пример получения роли:
import { roles } from "aria-query";
const buttonRole = roles.get("button");
console.log(buttonRole);
Объект роли содержит несколько важных свойств:
| Свойство | Описание |
|---|---|
props |
допустимые ARIA-атрибуты |
requiredProps |
обязательные свойства |
superClass |
родительские роли |
accessibleNameRequired |
необходимость доступного имени |
Пример структуры:
{
props: {
"aria-disabled": null,
"aria-expanded": null,
"aria-haspopup": null
},
requiredProps: {},
superClass: [
["roletype", "widget", "command"]
]
}
Такая структура позволяет анализировать корректность применения роли.
Коллекция elementRoles определяет, какие роли
автоматически назначаются HTML-элементам.
Пример получения ролей для элемента:
import { elementRoles } from "aria-query";
for (const [element, roles] of elementRoles) {
console.log(element, roles);
}
Элемент представлен объектом:
{
name: "button",
attributes: []
}
А роли представлены множеством:
Set { "button" }
Таким образом библиотека хранит правила:
<button> → role="button"
<nav> → role="navigation"
<ul> → role="list"
Это важно при анализе избыточных ARIA ролей.
Коллекция roleElements позволяет определить, какие HTML
элементы соответствуют конкретной роли.
Пример:
import { roleElements } from "aria-query";
const elements = roleElements.get("button");
console.log(elements);
Результат:
Set {
{ name: "button" },
{ name: "input", attributes: [{ name: "type", value: "button" }] },
{ name: "input", attributes: [{ name: "type", value: "submit" }] }
}
Это означает, что роль button уже реализована нативными
элементами.
Использование role="button" на div
допустимо, но менее предпочтительно, чем использование
button.
При создании кастомных компонентов необходимо проверять допустимость ролей.
Пример проверки существования роли:
import { roles } from "aria-query";
function isValidRole(role) {
return roles.has(role);
}
Использование:
isValidRole("button"); // true
isValidRole("unknown"); // false
Это может применяться в системах статического анализа.
Каждая роль поддерживает ограниченный набор ARIA атрибутов.
Получение списка допустимых атрибутов:
const role = roles.get("checkbox");
const props = Object.keys(role.props);
console.log(props);
Результат:
[
"aria-checked",
"aria-readonly",
"aria-required"
]
Такой анализ позволяет выявлять ошибки:
role="checkbox" aria-expanded="true"
aria-expanded не поддерживается ролью
checkbox.
Кастомная кнопка часто реализуется на основе div.
Пример:
<div role="button" tabindex="0">
Отправить
</div>
Основные требования:
JavaScript обработка:
const button = document.querySelector("[role='button']");
button.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
button.click();
}
});
Использование aria-query позволяет проверить допустимость роли.
if (!roles.has("button")) {
throw new Error("Invalid ARIA role");
}
Нативный input type="checkbox" уже доступен. Однако
иногда используется кастомная реализация.
HTML:
<div
role="checkbox"
aria-checked="false"
tabindex="0"
>
Подписаться
</div>
Изменение состояния:
const checkbox = document.querySelector("[role='checkbox']");
checkbox.addEventListener("click", toggle);
checkbox.addEventListener("keydown", (e) => {
if (e.key === " ") {
e.preventDefault();
toggle();
}
});
function toggle() {
const checked = checkbox.getAttribute("aria-checked") === "true";
checkbox.setAttribute("aria-checked", !checked);
}
Использование aria-query для проверки роли:
const role = roles.get("checkbox");
console.log(role.requiredProps);
Некоторые роли требуют обязательные свойства.
Некоторые роли требуют определённых атрибутов.
Пример:
const role = roles.get("slider");
console.log(role.requiredProps);
Результат:
{
"aria-valuemin": null,
"aria-valuemax": null,
"aria-valuenow": null
}
Создание компонента slider без этих атрибутов нарушает
правила доступности.
Пример корректного элемента:
<div
role="slider"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
tabindex="0"
></div>
Библиотека широко используется в инструментах проверки доступности.
Например, правило ESLint может проверять:
Пример псевдоправила:
function validateRole(node) {
const role = node.attributes.role;
if (!roles.has(role)) {
report("Unknown ARIA role");
}
}
Проверка атрибутов:
function validateProps(roleName, attributes) {
const role = roles.get(roleName);
const allowedProps = Object.keys(role.props);
attributes.forEach(attr => {
if (attr.startsWith("aria-") && !allowedProps.includes(attr)) {
report("Invalid ARIA attribute for role");
}
});
}
Использование ARIA не должно дублировать нативную семантику.
Неправильный пример:
<button role="button">Отправить</button>
Проверка через elementRoles:
function hasImplicitRole(elementName, role) {
for (const [element, rolesSet] of elementRoles) {
if (element.name === elementName && rolesSet.has(role)) {
return true;
}
}
}
Если роль уже назначается автоматически, указывать её не требуется.
aria-query может использоваться для автоматического генератора компонентов.
Пример генерации структуры для роли:
function generateComponent(roleName) {
const role = roles.get(roleName);
return {
role: roleName,
requiredProps: role.requiredProps,
supportedProps: Object.keys(role.props)
};
}
Результат:
{
role: "slider",
requiredProps: [
"aria-valuemin",
"aria-valuemax",
"aria-valuenow"
]
}
Такой механизм используется в системах дизайн-компонентов.
Меню требует строгой структуры ролей.
Пример:
<div role="menu">
<div role="menuitem" tabindex="0">Открыть</div>
<div role="menuitem" tabindex="0">Сохранить</div>
</div>
Проверка роли:
roles.has("menu");
roles.has("menuitem");
Анализ поддерживаемых свойств:
const menuItem = roles.get("menuitem");
console.log(menuItem.props);
Роль menuitem поддерживает:
aria-disabled
aria-expanded
aria-haspopup
aria-checked
При разработке компонентов рекомендуется соблюдать следующие принципы:
1. Использование нативных элементов
Предпочтение button, input,
select.
2. Добавление ARIA только при необходимости
ARIA не должна заменять HTML.
3. Поддержка клавиатурной навигации
Основные клавиши:
4. Поддержка состояний
Через ARIA:
aria-expanded
aria-pressed
aria-selected
aria-hidden
5. Проверка допустимости ролей
Через aria-query.
aria-query может использоваться внутри библиотек компонентов:
Библиотека позволяет:
Пример внутреннего валидатора:
export function validateAccessibility(role, props) {
if (!roles.has(role)) {
throw new Error("Invalid role");
}
const roleDefinition = roles.get(role);
for (const prop of Object.keys(props)) {
if (prop.startsWith("aria-") && !roleDefinition.props[prop]) {
throw new Error(`Unsupported attribute ${prop}`);
}
}
}
Такая проверка помогает гарантировать, что создаваемые компоненты соответствуют стандартам доступности и спецификации ARIA.