ARIA-атрибуты

Библиотека Slim Select заменяет стандартный HTML-элемент <select> собственной DOM-структурой. После такой замены часть встроенной семантики браузера теряется, поэтому корректная настройка ARIA-атрибутов становится обязательной частью разработки доступного интерфейса.

ARIA (Accessible Rich Internet Applications) обеспечивает передачу информации вспомогательным технологиям: экранным дикторам, системам голосового управления, навигации с клавиатуры и другим инструментам доступности.

Без правильно настроенных ARIA-атрибутов пользователь может столкнуться со следующими проблемами:

  • отсутствие информации о текущем состоянии списка;
  • невозможность понять, раскрыт ли список;
  • потеря информации о выбранном элементе;
  • отсутствие связи между полем и подписью;
  • некорректное озвучивание интерфейса screen reader’ом;
  • проблемы навигации с клавиатуры.

Стандартная семантика <select> и проблема кастомизации

Обычный HTML-элемент:

<select>
  <option>JavaScript</option>
  <option>TypeScript</option>
</select>

уже содержит встроенную семантику:

  • браузер знает, что это список;
  • screen reader воспринимает элемент как выпадающее меню;
  • клавиатурная навигация работает автоматически;
  • выбранное значение озвучивается системой доступности.

После инициализации Slim Select структура становится значительно сложнее:

<div class="ss-main">
  <div class="ss-single-selected">JavaScript</div>

  <div class="ss-content">
    <div class="ss-option">JavaScript</div>
    <div class="ss-option">TypeScript</div>
  </div>
</div>

Обычные <div> не имеют семантики. Для браузера и screen reader это просто контейнеры. Именно поэтому требуется ручное назначение ролей и ARIA-атрибутов.


Основные ARIA-атрибуты в Slim Select

Наиболее важные атрибуты:

Атрибут Назначение
aria-expanded состояние раскрытия списка
aria-selected выбран ли элемент
aria-disabled отключён ли элемент
aria-labelledby связь с заголовком
aria-label текстовое описание
aria-controls связь управляющего элемента со списком
aria-activedescendant активный элемент
aria-multiselectable поддержка множественного выбора
aria-hidden скрытие элементов от screen reader
aria-live озвучивание динамических изменений

aria-expanded

Назначение

Атрибут сообщает, раскрыт ли выпадающий список.

<div
  class="ss-main"
  aria-expanded="false">
</div>

После открытия:

<div
  class="ss-main"
  aria-expanded="true">
</div>

Практическое значение

Screen reader может озвучивать:

Список свернут

или:

Список раскрыт

Без этого пользователь не понимает текущее состояние интерфейса.


Обновление состояния

При ручной кастомизации Slim Select атрибут необходимо синхронизировать с состоянием компонента.

const trigger = document.querySelector('.ss-main')

trigger.setAttribute('aria-expanded', 'true')

При закрытии:

trigger.setAttribute('aria-expanded', 'false')

aria-selected

Назначение

Показывает, выбран ли конкретный элемент списка.

<div
  class="ss-option"
  aria-selected="true">
  JavaScript
</div>

Для остальных элементов:

<div
  class="ss-option"
  aria-selected="false">
  TypeScript
</div>

Работа screen reader

При перемещении по списку диктор может озвучивать:

JavaScript, выбран

или:

TypeScript, не выбран

Синхронизация состояния

При изменении выбора необходимо обновлять атрибут:

option.setAttribute('aria-selected', 'true')

С остальных элементов атрибут также должен обновляться:

options.forEach(item => {
  item.setAttribute('aria-selected', 'false')
})

aria-disabled

Блокировка элементов

ARIA сообщает, что элемент недоступен для взаимодействия.

<div
  class="ss-option"
  aria-disabled="true">
  Недоступный пункт
</div>

Разница между disabled и aria-disabled

disabled

Полностью блокирует элемент на уровне браузера.

<select disabled>

aria-disabled

Только сообщает состояние доступности.

<div aria-disabled="true">

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


Комплексное отключение

option.setAttribute('aria-disabled', 'true')
option.classList.add('disabled')

aria-label

Текстовое описание без видимого заголовка

Если у поля отсутствует <label>, требуется добавить текстовое описание вручную.

<div
  class="ss-main"
  aria-label="Выбор языка программирования">
</div>

Когда использовать

Подходит для:

  • иконок;
  • компактных форм;
  • кастомных UI-компонентов;
  • скрытых заголовков;
  • интерфейсов без визуального текста.

Проблема отсутствующего описания

Без aria-label screen reader может озвучивать:

Кнопка

вместо:

Выбор языка программирования

aria-labelledby

Связь с внешним заголовком

Наиболее предпочтительный способ описания поля.

<label id="skills-label">
  Навыки
</label>

<div
  class="ss-main"
  aria-labelledby="skills-label">
</div>

Преимущества перед aria-label

aria-labelledby:

  • использует существующий текст;
  • поддерживает локализацию;
  • автоматически обновляется;
  • лучше воспринимается screen reader’ами.

Использование нескольких заголовков

Допускается объединение нескольких идентификаторов.

<div
  aria-labelledby="title description">
</div>

aria-controls

Связь кнопки и списка

Указывает, какой элемент контролируется текущим компонентом.

<div
  class="ss-main"
  aria-controls="skills-list">
</div>

<div id="skills-list">
</div>

Практическая польза

Screen reader понимает:

  • какой список открывается;
  • какой элемент связан с контролом;
  • где находится управляемая область.

aria-activedescendant

Активный элемент навигации

При перемещении стрелками необходимо указывать текущий активный пункт.

<div
  class="ss-content"
  aria-activedescendant="option-js">
</div>

<div id="option-js">
  JavaScript
</div>

Работа клавиатурной навигации

При нажатии стрелки вниз:

list.setAttribute(
  'aria-activedescendant',
  'option-ts'
)

Почему это важно

Screen reader получает информацию:

TypeScript

при перемещении по списку.

Без атрибута навигация становится «немой».


aria-multiselectable

Поддержка множественного выбора

Для multi-select необходимо указать возможность выбора нескольких элементов.

<div
  role="listbox"
  aria-multiselectable="true">
</div>

Поведение screen reader

Диктор может сообщить:

Список с множественным выбором

Сочетание с aria-selected

Каждый элемент должен иметь собственное состояние:

<div aria-selected="true">
  JavaScript
</div>

<div aria-selected="false">
  Vue
</div>

aria-hidden

Скрытие декоративных элементов

Некоторые части интерфейса не должны озвучиваться.

Пример:

<span
  class="icon"
  aria-hidden="true">
  ▼
</span>

Когда применять

Подходит для:

  • декоративных иконок;
  • SVG без смысловой нагрузки;
  • визуальных разделителей;
  • дублирующего текста.

Ошибочное использование

Нельзя скрывать:

  • активные кнопки;
  • поля формы;
  • элементы навигации;
  • важный текст.

aria-live

Озвучивание динамических изменений

Используется для уведомлений о смене состояния.

<div aria-live="polite"></div>

Пример обновления

status.innerText =
  'Выбрано 3 элемента'

Screen reader автоматически озвучит изменение.


Режимы работы

Значение Поведение
polite мягкое уведомление
assertive немедленное озвучивание

ARIA-роли в Slim Select

Атрибуты работают совместно с ролями.


role="combobox"

Главный контрол выбора.

<div
  role="combobox"
  aria-expanded="false">
</div>

role="listbox"

Контейнер списка.

<div role="listbox">
</div>

role="option"

Элемент списка.

<div
  role="option"
  aria-selected="true">
  JavaScript
</div>

Полноценный пример доступного Slim Select

<label id="framework-label">
  Фреймворк
</label>

<div
  class="ss-main"
  role="combobox"
  aria-expanded="false"
  aria-labelledby="framework-label"
  aria-controls="framework-list">

  React
</div>

<div
  id="framework-list"
  role="listbox">

  <div
    id="react-option"
    role="option"
    aria-selected="true">

    React
  </div>

  <div
    id="vue-option"
    role="option"
    aria-selected="false">

    Vue
  </div>

</div>

Интеграция с Slim Select

Инициализация

new SlimSelect({
  select: '#skills'
})

Добавление кастомных ARIA-атрибутов

const main =
  document.querySelector('.ss-main')

main.setAttribute(
  'aria-label',
  'Выбор навыков'
)

Управление состоянием

main.addEventListener('click', () => {

  const expanded =
    main.getAttribute('aria-expanded')

  main.setAttribute(
    'aria-expanded',
    expanded === 'true'
      ? 'false'
      : 'true'
  )

})

Доступность поиска внутри Slim Select

Slim Select поддерживает встроенный поиск.

Поле поиска также требует ARIA-настройки.


Пример

<input
  type="search"
  aria-label="Поиск навыков">

Связь с результатами

<input
  aria-controls="search-results">

Ошибки доступности

Отсутствие ролей

Плохо:

<div class="ss-option">

Правильно:

<div
  role="option">

Несинхронизированный aria-expanded

Ошибка:

aria-expanded="false"

при открытом списке.


Отсутствие aria-selected

Screen reader не понимает текущее состояние выбора.


Скрытие активных элементов через aria-hidden

Ошибка:

<button aria-hidden="true">

Такой элемент становится невидимым для систем доступности.


Проверка доступности

Проверка клавиатурой

Необходимо контролировать:

  • Tab;
  • Shift + Tab;
  • Enter;
  • Escape;
  • Arrow Up;
  • Arrow Down.

Проверка screen reader

Наиболее распространённые инструменты:

Screen Reader Платформа
NVDA Windows
JAWS Windows
VoiceOver macOS
TalkBack Android

Lighthouse

Инструмент Chrome DevTools автоматически проверяет:

  • ARIA-атрибуты;
  • контрастность;
  • семантику;
  • доступность форм.

Расширенная кастомизация

Генерация ID для aria-controls

const listId =
  'ss-list-' + Date.now()

list.id = listId

main.setAttribute(
  'aria-controls',
  listId
)

Автоматическое обновление aria-selected

function updateSelected(option) {

  document
    .querySelectorAll('.ss-option')
    .forEach(item => {

      item.setAttribute(
        'aria-selected',
        'false'
      )

    })

  option.setAttribute(
    'aria-selected',
    'true'
  )

}

Динамическое уведомление через aria-live

<div
  id="status"
  aria-live="polite">
</div>
status.textContent =
  'Выбрано: React'

Совместимость ARIA и браузеров

Большинство современных браузеров поддерживают:

  • Chrome;
  • Firefox;
  • Safari;
  • Edge.

Однако поведение screen reader может различаться.

Например:

  • NVDA лучше работает с Firefox;
  • VoiceOver тесно интегрирован с Safari;
  • JAWS имеет собственные особенности обработки combobox.

Практические рекомендации

Использование нативного <select> при возможности

Если сложная кастомизация не требуется, стандартный <select> остаётся наиболее доступным решением.


Минимизация DOM-сложности

Чрезмерная вложенность:

<div>
  <div>
    <div>
      <div>

усложняет навигацию вспомогательным технологиям.


Синхронное обновление состояний

ARIA-атрибуты должны изменяться одновременно с визуальным состоянием интерфейса.


Обязательное тестирование клавиатурой

Компонент считается недоступным, если:

  • невозможно открыть список без мыши;
  • отсутствует навигация стрелками;
  • Enter не выбирает значение;
  • Escape не закрывает список.

Контроль фокуса

При открытии списка фокус должен переходить в активную область.

searchInput.focus()

Видимый фокус

Нельзя полностью отключать outline:

Плохо:

*:focus {
  outline: none;
}

Лучше:

.ss-option:focus {
  outline: 2px solid #3b82f6;
}