В доступных веб-интерфейсах роль элемента определяет его семантику для вспомогательных технологий. Роли используются экранными дикторами, системами навигации по интерфейсу, инструментами тестирования доступности и браузерными API. В условиях современного JavaScript-приложения, где интерфейс часто изменяется без полной перезагрузки страницы, роль элемента может потребовать изменения во время выполнения программы.
Библиотека aria-query предоставляет структурированные данные о ролях, свойствах ARIA, связанных элементах и допустимых атрибутах. Она не управляет DOM напрямую, но служит источником достоверной информации о семантике. На основе этих данных создаются механизмы динамического назначения ролей, проверки корректности интерфейса и автоматизации доступности.
Динамическое управление ролями включает:
Библиотека содержит несколько основных коллекций данных:
Каждая роль содержит метаданные:
Доступ к ролям осуществляется через объект roles.
import { roles } from "aria-query";
const buttonRole = roles.get("button");
console.log(buttonRole);
Полученный объект содержит структуру, описывающую роль:
{
abstract: false,
requiredProps: {},
supportedProps: {
"aria-disabled": null,
"aria-expanded": null
},
superclassRoles: [["command"]],
}
Такая структура используется для динамической проверки корректности роли.
Перед динамическим назначением роли требуется определить, допустима ли она для конкретного HTML-элемента. Для этого используется коллекция elementRoles.
import { elementRoles } from "aria-query";
for (const [element, roles] of elementRoles) {
console.log(element, roles);
}
Элемент описывается объектом:
{
name: "button",
attributes: []
}
Связанные роли возвращаются в виде Set.
Пример проверки:
function isRoleAllowed(tagName, roleName) {
for (const [element, roles] of elementRoles) {
if (element.name === tagName && roles.has(roleName)) {
return true;
}
}
return false;
}
Применение:
isRoleAllowed("button", "button");
isRoleAllowed("button", "navigation");
Такой механизм предотвращает назначение некорректных ролей.
Современные интерфейсы часто меняют семантику элемента в зависимости от состояния. Пример — кнопка, превращающаяся в переключатель, или список, преобразующийся в меню.
Простейшее изменение роли осуществляется через атрибут
role.
const element = document.querySelector(".control");
element.setAttribute("role", "button");
Однако перед установкой роли может выполняться проверка через aria-query.
function setAccessibleRole(element, roleName) {
const tag = element.tagName.toLowerCase();
if (!isRoleAllowed(tag, roleName)) {
throw new Error(`Role ${roleName} not allowed for <${tag}>`);
}
element.setAttribute("role", roleName);
}
Использование:
setAccessibleRole(element, "button");
Некоторые роли требуют обязательных атрибутов. Например,
slider требует aria-valuemin,
aria-valuemax и aria-valuenow.
Получение списка обязательных атрибутов:
import { roles } from "aria-query";
const sliderRole = roles.get("slider");
console.log(sliderRole.requiredProps);
Проверка:
function validateRequiredProps(element, roleName) {
const role = roles.get(roleName);
const required = Object.keys(role.requiredProps);
for (const prop of required) {
if (!element.hasAttribute(prop)) {
return false;
}
}
return true;
}
Использование:
if (!validateRequiredProps(element, "slider")) {
console.warn("Missing required ARIA properties");
}
Во многих интерфейсах роль элемента зависит от состояния приложения. Например, компонент может переключаться между режимами.
Пример:
buttonswitchfunction updateRole(element, state) {
if (state === "toggle") {
setAccessibleRole(element, "switch");
} else {
setAccessibleRole(element, "button");
}
}
Использование:
updateRole(control, "toggle");
В реактивных системах (например, в архитектуре с централизованным состоянием) роль может изменяться автоматически при изменении данных.
Пример упрощённого состояния:
const state = {
isExpanded: false
};
Обновление роли:
function syncRole(element, state) {
const role = state.isExpanded ? "button" : "link";
setAccessibleRole(element, role);
}
Каждое изменение состояния вызывает синхронизацию:
state.isExpanded = true;
syncRole(element, state);
ARIA-роль поддерживает только определённые свойства. Информация о них также хранится в aria-query.
Получение списка:
const role = roles.get("checkbox");
console.log(role.supportedProps);
Результат:
{
"aria-checked": null,
"aria-readonly": null,
"aria-required": null
}
Проверка атрибутов:
function validateProps(element, roleName) {
const role = roles.get(roleName);
const supported = Object.keys(role.supportedProps);
for (const attr of element.getAttributeNames()) {
if (attr.startsWith("aria-") && !supported.includes(attr)) {
console.warn(`Attribute ${attr} not supported by role ${roleName}`);
}
}
}
Интерактивные компоненты могут включать множество вложенных элементов. Например:
При изменении состояния компонента необходимо корректно обновлять роли нескольких элементов.
Пример структуры:
<div class="menu">
<button class="menu-toggle"></button>
<ul class="menu-list"></ul>
</div>
При открытии меню:
toggle.setAttribute("aria-expanded", "true");
list.setAttribute("role", "menu");
При закрытии:
toggle.setAttribute("aria-expanded", "false");
list.removeAttribute("role");
Перед назначением роли меню может выполняться проверка через aria-query.
setAccessibleRole(list, "menu");
При частых изменениях интерфейса важно минимизировать количество обращений к структурам aria-query. Рекомендуется кэшировать данные ролей.
const roleCache = new Map();
function getRole(roleName) {
if (!roleCache.has(roleName)) {
roleCache.set(roleName, roles.get(roleName));
}
return roleCache.get(roleName);
}
Использование:
const role = getRole("dialog");
Кэширование уменьшает количество операций поиска в коллекциях.
aria-query часто используется в системах линтинга и тестирования доступности. Динамическое управление ролями может сопровождаться проверками.
Алгоритм валидации:
Пример функции:
function validateElement(element) {
const roleName = element.getAttribute("role");
if (!roleName) return;
const role = roles.get(roleName);
if (!role) {
throw new Error(`Unknown role: ${roleName}`);
}
if (!validateRequiredProps(element, roleName)) {
console.error("Missing required ARIA attributes");
}
validateProps(element, roleName);
}
Использование:
validateElement(document.querySelector(".component"));
Некоторые роли наследуют поведение других ролей. Эта информация
хранится в superclassRoles.
Пример:
const role = roles.get("checkbox");
console.log(role.superclassRoles);
Результат:
[
["input"]
]
Эта иерархия используется при анализе доступности и построении семантических моделей интерфейса.
Пример функции проверки:
function isSubclass(roleName, parentRole) {
const role = roles.get(roleName);
return role.superclassRoles.some(group =>
group.includes(parentRole)
);
}
В UI-библиотеках динамическое управление ролями часто интегрируется непосредственно в компоненты.
Пример компонента:
class ToggleButton {
constructor(element) {
this.element = element;
this.active = false;
}
toggle() {
this.active = !this.active;
const role = this.active ? "switch" : "button";
setAccessibleRole(this.element, role);
}
}
Создание экземпляра:
const toggle = new ToggleButton(
document.querySelector(".toggle")
);
Переключение:
toggle.toggle();
Использование aria-query позволяет контролировать корректность роли независимо от конкретного фреймворка или архитектуры интерфейса.
Динамическое управление ролями играет ключевую роль в:
aria-query служит источником официальных данных спецификации ARIA. Благодаря этому любые изменения ролей могут автоматически проверяться на соответствие стандарту.
Такой подход позволяет строить сложные интерфейсы с динамической семантикой, не нарушая требований доступности и обеспечивая корректную работу вспомогательных технологий.