В библиотеке Tom Select доступность строится вокруг корректного
использования ARIA-атрибутов, которые обеспечивают взаимодействие с
экранными считывателями и вспомогательными технологиями. Основная цель
ARIA в данном контексте — превратить кастомный UI-компонент в
семантически эквивалентный нативному <select>
элементу.
Ключевая идея заключается в том, что визуально компонент может быть полностью переопределён, но логическая модель должна оставаться прозрачной для assistive technologies. Для этого Tom Select динамически управляет набором ARIA-атрибутов на основе текущего состояния.
После инициализации Tom Select формирует DOM-структуру, где основной контейнер получает роль интерактивного комбинированного элемента:
role="combobox" — обозначает поле ввода с выпадающим
спискомaria-expanded — отражает состояние раскрытия
спискаaria-haspopup="listbox" — указывает наличие связанного
списка опцийaria-controls — связывает input с элементом спискаaria-activedescendant — управляет текущим выделенным
элементомПример упрощённой структуры:
<div class="ts-wrapper">
<input
type="text"
role="combobox"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="ts-dropdown"
aria-activedescendant=""
/>
<div role="listbox" id="ts-dropdown"></div>
</div>
Состояние раскрытия является ключевым для ARIA-логики. При открытии dropdown:
aria-expanded устанавливается в truearia-controlsПри закрытии:
aria-expanded становится falsearia-activedescendant очищается или сбрасываетсяФрагмент логики обновления:
function setDropdownState(isOpen) {
const input = this.control_input;
input.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
if (!isOpen) {
input.setAttribute('aria-activedescendant', '');
}
}
Элементы списка в Tom Select получают роль option внутри
контейнера listbox.
Каждый элемент:
role="option"aria-selected="true|false"id для связывания с
aria-activedescendantПример:
<div role="listbox" id="ts-dropdown">
<div role="option" id="opt-1" aria-selected="false">Option 1</div>
<div role="option" id="opt-2" aria-selected="true">Option 2</div>
</div>
При навигации клавиатурой Tom Select изменяет
aria-activedescendant, не перемещая фокус внутрь списка.
Это важная техника, позволяющая сохранить контроль над input.
В режиме множественного выбора (maxItems > 1)
ARIA-логика усложняется, так как несколько элементов могут быть выбраны
одновременно.
Каждый выбранный элемент:
aria-selected="true"itemsУдаление элемента автоматически обновляет атрибут:
function updateOptionState(option, selected) {
option.setAttribute('aria-selected', selected ? 'true' : 'false');
}
Важно, что ARIA-состояние должно всегда строго соответствовать внутреннему массиву выбранных значений.
Одним из ключевых механизмов доступности является
aria-activedescendant. Он позволяет управлять фокусом
внутри списка без фактического перемещения фокуса DOM.
При нажатии стрелок:
id записывается в
aria-activedescendantПример логики:
function setActiveOption(option) {
const input = this.control_input;
if (!option) return;
input.setAttribute(
'aria-activedescendant',
option.getAttribute('id')
);
}
Такой подход обеспечивает:
Tom Select использует:
aria-autocomplete="list"Это означает, что ввод пользователя сопровождается отображением
списка возможных значений. В отличие от inline или
both, режим list предполагает, что
автодополнение происходит через dropdown.
При фильтрации списка значение ARIA-атрибутов не меняется, но
обновляется содержимое listbox.
При каждом вводе текста происходит:
Критически важно, чтобы:
idaria-selected соответствовал состоянию данныхaria-activedescendant не ссылался на удалённый
элементПример генерации:
function renderOption(item) {
const el = document.createElement('div');
el.setAttribute('role', 'option');
el.setAttribute('id', `opt-${item.value}`);
el.setAttribute('aria-selected', 'false');
el.textContent = item.text;
return el;
}
В режиме тегов каждый созданный элемент становится интерактивной сущностью.
Для доступности используются:
aria-label для удаленияrole="button" или встроенные controlsaria-live для уведомления об измененияхПример тега:
<div class="item" aria-label="Remove tag JavaScript" role="button">
JavaScript
</div>
Удаление тега должно сопровождаться обновлением списка доступных опций и синхронизацией ARIA-атрибутов.
Для динамических изменений (например, добавление/удаление элементов)
используется aria-live:
aria-live="polite" — ненавязчивые уведомленияaria-live="assertive" — срочные измененияПример:
<div aria-live="polite" class="ts-screenreader-status"></div>
Обновление статуса:
function announce(message) {
const region = document.querySelector('.ts-screenreader-status');
region.textContent = message;
}
Используется для сообщений:
Отключённые элементы должны корректно отражаться в доступности:
aria-disabled="true"function setDisabled(option, isDisabled) {
option.setAttribute('aria-disabled', isDisabled ? 'true' : 'false');
}
Важно, что disabled в ARIA не всегда равен
HTML-disabled: логика навигации должна учитывать оба состояния.
При использовании групп (optgroups) структура
дополняется:
role="group"aria-label или aria-labelledbyПример:
<div role="group" aria-label="Frontend">
<div role="option">React</div>
<div role="option">Vue</div>
</div>
Это позволяет screen reader’ам логически разделять наборы данных.
Одной из сложностей реализации является постоянная синхронизация:
Любое рассогласование приводит к некорректному поведению вспомогательных технологий.
Типичная проблема — удалённый option остаётся в
aria-activedescendant. Решение заключается в
централизованной функции синхронизации:
function syncAriaState() {
updateExpandedState();
updateSelectedOptions();
updateActiveDescendant();
}
Клавиатурная модель тесно связана с ARIA:
aria-activedescendantaria-selectedКаждое действие сопровождается обновлением соответствующих атрибутов, что делает компонент предсказуемым для assistive technologies.
При расширении Tom Select часто встречаются ошибки:
id у optionsaria-selected и фактического
состоянияaria-live областейТакие проблемы приводят к тому, что компонент становится недоступным, несмотря на внешне корректный интерфейс.
Внутренняя модель Tom Select строится как связка:
Эта структура обеспечивает поведение, эквивалентное нативному
<select>, при сохранении полной кастомизации
интерфейса и расширяемости логики.