Работа с программами чтения экрана

Базовая модель доступности и роль поля ввода

Календарный компонент в первую очередь взаимодействует не с самим календарём, а с текстовым полем ввода. Именно это поле становится основным источником информации для программ чтения экрана, таких как NVDA, JAWS и VoiceOver.

Корректная доступность начинается с семантики input-элемента:

  • наличие связанного <label>
  • корректный aria-label или aria-labelledby
  • указание формата даты через aria-describedby
  • явное описание ожидаемого поведения поля

Пример базовой разметки:

<label for="date_input">Дата</label>
<input
  id="date_input"
  type="text"
  aria-describedby="date_help"
>
<div id="date_help">Формат: день.месяц.год</div>

При инициализации Flatpickr это поле остаётся центральным элементом, а календарь рассматривается как вспомогательный интерфейс.


ARIA-атрибуты, используемые в календарной сетке

Визуальный календарь строится как таблица дат, однако для программ чтения экрана требуется строгая ARIA-структура:

  • role="dialog" — контейнер календаря
  • aria-modal="true" — фиксация модального поведения
  • role="grid" — календарная сетка
  • role="gridcell" — отдельная дата
  • aria-selected="true" — выбранная дата
  • aria-disabled="true" — недоступные даты

Структурный пример:

<div class="flatpickr-calendar" role="dialog" aria-modal="true">
  <div class="dayContainer" role="grid">
    <div role="row">
      <span role="gridcell" aria-selected="false">1</span>
      <span role="gridcell" aria-selected="true">2</span>
    </div>
  </div>
</div>

Для VoiceOver и NVDA критично, чтобы каждая ячейка имела корректное текстовое представление: день недели, число и состояние (выбран/недоступен).


Фокус-менеджмент и логика перемещения

Календарь должен управлять фокусом предсказуемо:

  1. Открытие календаря переводит фокус внутрь grid
  2. Стрелки перемещают активную дату
  3. Enter выбирает дату
  4. Escape закрывает календарь и возвращает фокус в input

В JAWS некорректный фокус приводит к «потере контекста», когда пользователь оказывается вне календарной сетки без понимания текущей позиции.

Типичная логика:

onOpen: function(selectedDates, dateStr, instance) {
  instance.calendarContainer.setAttribute("role", "dialog");
  instance.calendarContainer.setAttribute("aria-modal", "true");

  instance.jumpToDate(selectedDates[0] || new Date());
}

Ключевой момент — активная дата должна всегда иметь фокус или быть программно обозначенной через tabindex="0".


Доступное объявление изменений (aria-live)

Программы чтения экрана требуют динамического оповещения о смене состояния календаря. Для этого используется aria-live.

Типовые сценарии:

  • смена месяца
  • выбор даты
  • переключение диапазона дат
  • блокировка недоступных дат

Рекомендуемая структура:

<div id="fp_live_region" aria-live="polite" aria-atomic="true"></div>

При изменении месяца:

function announceMonth(monthName) {
  document.getElementById("fp_live_region").textContent =
    "Текущий месяц: " + monthName;
}

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


Семантика кнопок навигации

Кнопки переключения месяца должны быть однозначно идентифицируемыми:

  • «Следующий месяц»
  • «Предыдущий месяц»
  • «Выбрать год»

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

Корректный вариант:

<button aria-label="Предыдущий месяц">‹</button>
<button aria-label="Следующий месяц">›</button>

При использовании JAWS без aria-label кнопка будет озвучена как «button», что делает навигацию непрозрачной.


Роль клавиатурной модели для screen reader пользователей

Хотя программы чтения экрана обеспечивают озвучивание, управление остаётся клавиатурным. Поэтому необходимо соблюдать единый набор действий:

  • ArrowLeft / ArrowRight — смена дня
  • ArrowUp / ArrowDown — смена недели
  • PageUp / PageDown — смена месяца
  • Home / End — начало и конец недели

Важно, чтобы активная дата всегда имела:

  • визуальный фокус
  • ARIA-обозначение текущей позиции
onKeyDown: function(_, __, ___, e) {
  if (e.key === "ArrowRight") {
    this.changeDate(1);
  }
}

VoiceOver синхронизирует фокус с визуальным выделением, поэтому рассинхронизация приводит к некорректному озвучиванию.


Объявление выбранной даты

После выбора даты необходимо обеспечить двойное представление:

  1. Обновление input value
  2. Озвучивание через live region

Пример:

onChange: function(selectedDates, dateStr, instance) {
  instance.input.value = dateStr;

  document.getElementById("fp_live_region").textContent =
    "Выбрана дата: " + dateStr;
}

В NVDA это позволяет мгновенно подтвердить действие без необходимости повторного наведения на поле.


Работа с недоступными датами

Отключённые даты должны быть явно помечены:

  • aria-disabled="true"
  • отсутствие фокуса
  • исключение из tab-order
<span role="gridcell" aria-disabled="true">13</span>

JAWS корректно сообщает такие элементы как «disabled», однако только при наличии атрибута aria-disabled.


Локализация и голосовое представление дат

Формат даты влияет на то, как она озвучивается. Например:

  • 01.02.2026 может быть прочитано как «первое февраля»
  • 02/01/2026 может интерпретироваться неоднозначно

VoiceOver использует системную локаль, поэтому важно синхронизировать:

  • locale в Flatpickr
  • формат отображения в input
  • формат live region сообщений

Обработка множественных и диапазонных выборов

При режимах multiple и range требуется расширенная семантика:

  • multiple: перечисление выбранных дат
  • range: старт и конец диапазона

Пример объявления диапазона:

onChange: function(selectedDates, dateStr) {
  const msg = selectedDates.length === 2
    ? "Диапазон выбран: " + dateStr
    : "Выбрана дата: " + dateStr;

  document.getElementById("fp_live_region").textContent = msg;
}

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


Типичные ошибки интеграции с screen readers

  • отсутствие aria-label у кнопок навигации
  • отсутствие live region
  • потеря фокуса при открытии календаря
  • использование только визуального выделения без ARIA-состояний
  • несогласованность input value и выбранной даты

JAWS особенно чувствителен к потере фокуса и может «выпадать» из календаря при неправильной обработке DOM-обновлений.


Согласованность состояния между DOM и доступной моделью

Ключевой принцип — синхронизация трёх слоёв:

  1. DOM (визуальное представление)
  2. ARIA-дерево (доступная модель)
  3. Live announcements (динамические события)

В Flatpickr эта синхронизация достигается через обработчики событий изменения состояния и корректное управление атрибутами gridcell.

Любое рассогласование приводит к ситуации, когда экранный диктор озвучивает одно состояние, а пользователь видит другое, что критично для доступности интерфейса.