Как доработать AI DatePicker до WCAG: опыт и уроки | AiManual
AiManual Logo Ai / Manual.
09 Июл 2026 Гайд

Как доработать AI-сгенерированный DatePicker до WCAG: опыт 3 дней и уроки

Рассказываю, как за 3 дня довести AI-сгенерированный React DatePicker до соответствия WCAG. Реальные проблемы с NVDA, решения для ARIA и клавиатурной навигации.

Почему AI-календарь — это иллюзия готовности

Сначала всё выглядит красиво. Ты кидаешь промпт в Claude, GPT-4o или ещё кого-то, получаешь React-компонент с анимацией, иконками, кастомными стилями. Кажется — вот оно, счастье. Открываешь NVDA — тишина. Нажимаешь Tab — фокус прыгает на бог знает что. Выбираешь дату — скринридер молчит. Всё, что нагенерировал AI, разваливается в первой же проверке на доступность. И это не случайность — это фундаментальная проблема генеративных моделей: они не видят разницы между «красиво» и «доступно».

Предупреждение: если вы планируете использовать AI-сгенерированный DatePicker в продакшне без доработки — готовьтесь к судебным искам за нарушение доступности. В 2026 году это уже не шутка.

Я потратил три дня на то, чтобы довести такой компонент до соответствия стандартам WCAG 2.2 (да, уже 2.2, а не 2.1). Ниже — хроника этого безумия с реальными кусками кода и комментариями: что сломал AI, как я это чинил, и что осталось за бортом.

День 1. NVDA показала всю правду

Код пришёл от Claude (3.5 Sonnet, 2025). На вид — огонь. Внутри — пустота. Скринридер просто проглатывал календарь. Первым делом я прогнал его через axe DevTools — 24 критических нарушения. Дальше — ручное тестирование с NVDA.

Главные проблемы первой версии:

  • Нет role="grid" или role="gridcell" — таблица была просто без семантики.
  • Кнопки переключения месяцев не имели aria-label — NVDA читала их как «кнопка» и всё.
  • Выбранная дата не объявлялась — ни через aria-selected, ни через aria-current="date".
  • Фокус не управлялся после выбора — пользователь застревал в календаре.
  • Сообщения об ошибках (если ввести невалидную дату) — просто текст в div, без role="alert".
  • 💡
    Инструменты для первого дня: NVDA (бесплатный, но лучший скринридер под Windows), axe DevTools, Lighthouse Accessibility Audit, и пара таблеток от головной боли.

    Я сделал скриншоты отчёта axe, подсветил каждое нарушение в коде и отправил обратно в Claude с просьбой исправить. Результат: он исправил часть, но сломал остальное — например, удалил aria-label у кнопок, заменив на пустой title. AI не понимает контекст доступности. Ему нужно явно диктовать каждую строчку WCAG.

    1 Ручное добавление ARIA-атрибутов

    Пришлось переписать DatePicker вручную. Критический минимум для DatePicker по WCAG 2.2:

    • Контейнер календаря — role="application" или role="dialog" (если это модалка).
    • Таблица с датами — role="grid", строки — role="row", ячейки — role="gridcell".
    • Заголовок текущего месяца и года — aria-live="polite" или обновление aria-label у контейнера.
    • Кнопки навигации — aria-label="Предыдущий месяц" и aria-label="Следующий месяц".
    • Выбранная дата — aria-selected="true" + визуальное выделение (не только цвет).
    • Сегодняшняя дата — aria-current="date".
    • Неактивные дни (вне месяца) — aria-disabled="true" + скрытие от скринридера через tabindex="-1" и aria-hidden.

    Пример того, как я переписал ячейку дня. Сначала AI выдавал:

    После ручной правки:

    ⚠️ Ошибка, которую я делал на старте: пытался объяснять AI, что не так. Он генерировал новый код, но не проверял его. Контролировать каждое изменение пришлось вручную, через diff и git.

    День 2. Клавиатурная навигация — ад под прикрытием

    AI сгенерировал обработчик onKeyDown, который ловил только Enter и Escape. А что со стрелками? Пользователь не может перемещаться по дням. WCAG требует, чтобы DatePicker поддерживал стрелки, Page Up/Down, Home/End.

    Как должно работать (согласно WAI-ARIA Authoring Practices):

    {day} {day}
    Клавиша Действие
    Стрелки вверх/вниз Перемещение на неделю
    Стрелки влево/вправо Соседний день
    Page Up / Page Down Предыдущий/следующий месяц
    Home / End Первый / последний день месяца
    Enter / Space Выбрать дату
    Escape Закрыть календарь
    Tab Уйти из календаря (фокус переходит к следующему элементу)

Реализация на React с хуками — дело нехитрое, но кропотливое. Я написал useGridKeyboardNavigation — кастомный хук, который управляет индексом активной ячейки. Вот фрагмент, который AI не смог сгенерировать корректно:

function handleKeyDown(e, dayIndex, days) {
  const rows = 6, cols = 7;
  let row = Math.floor(dayIndex / cols);
  let col = dayIndex % cols;

  switch (e.key) {
    case 'ArrowUp':
      row = Math.max(0, row - 1);
      break;
    case 'ArrowDown':
      row = Math.min(rows - 1, row + 1);
      break;
    case 'ArrowLeft':
      col = (col - 1 + cols) % cols;
      if (col === cols - 1) row = Math.max(0, row - 1);
      break;
    case 'ArrowRight':
      col = (col + 1) % cols;
      if (col === 0) row = Math.min(rows - 1, row + 1);
      break;
    case 'Home':
      col = 0;
      break;
    case 'End':
      col = cols - 1;
      break;
    case 'PageUp':
      e.preventDefault();
      goToPrevMonth();
      return;
    case 'PageDown':
      e.preventDefault();
      goToNextMonth();
      return;
    case 'Enter':
    case ' ':
      e.preventDefault();
      selectDate(dayIndex);
      return;
    case 'Escape':
      close();
      return;
    default:
      return;
  }
  e.preventDefault();
  const newIndex = row * cols + col;
  setFocusedIndex(newIndex);
}

После этого пришлось добавить aria-activedescendant и синхронизировать tabindex на контейнере. AI упорно пытался сделать каждую ячейку с tabindex="0" — это дичь, скринридер будет табить по всем 30+ дням.

День 3. Дьявол в деталях: live region, высокий контраст, мобильные скринридеры

Третий день ушёл на то, что не видно глазом, но слышно ушами (и ощущается пальцами). Вот список того, что AI проглотил:

2.1. aria-live для выбранной даты

Когда пользователь выбирает дату, нужно объявить её вслух. AI этого не делал. Я добавил невидимый

и обновлял его текст при выборе. Например: «Выбрана 15 марта 2026».

2.2. Высокий контраст и forced-colors

AI использовал box-shadow для выделения выбранного дня. В режиме Windows High Contrast тени пропадают. Пришлось добавить outline и border, а также медиа-запрос @media (forced-colors: active) — он переопределяет цвета под системную палитру.

@media (forced-colors: active) {
  .datepicker-day--selected {
    outline: 3px solid Highlight;
    outline-offset: -1px;
  }
}

2.3. TalkBack и VoiceOver

Я протестировал на Android TalkBack — AI-код не работал от слова совсем. Пришлось добавить aria-label на каждую дату в формате «15 марта 2026, выбранная» и выключить user-select, чтобы жест смахивания не выделял текст.

2.4. Поддержка reduced motion

AI обожал анимации: плавное появление календаря, переключение месяца с переворотом. Всё это должно отключаться при prefers-reduced-motion: reduce. Я обернул анимации в @media (prefers-reduced-motion: no-preference).

Сравнение: AI vs ручная доработка

Критерий AI (исходный) После ручной правки
ARIA семантика 0 правильных атрибутов Полное соответствие WAI-ARIA
Клавиатурная навигация Только Tab+Enter Стрелки, PageUp/Down, Home/End
Live-объявления Нет aria-live при выборе и ошибках
High Contrast Поломан forced-colors, outline
Reduced motion Игнорируется prefers-reduced-motion
NVDA / VoiceOver / TalkBack Минимальная поддержка (NVDA) Все три пройдены

Что я вынес из этого опыта

AI — отличный генератор скелета. Он даёт 80% кода, но оставшиеся 20% — это accessibility, которые в 2026 году уже не опция. Если вы думаете, что можно положиться на модель в вопросах доступности — вы ошибаетесь. Каждая новая версия Claude или GPT добавляет больше строк, но не больше понимания.

Мой конкретный совет: перед тем как писать промпт, составьте чек-лист WCAG для компонента. Прогоните его на сгенерированном коде. И будьте готовы переписать 30-40% вручную. Это нормально. Лучше потратить три дня сейчас, чем потом переделывать под судебным предписанием.

Кстати, я заметил, что AI неплохо справляется, если давать ему конкретные указания вроде «добавь aria-selected при условии» или «реализуй обработчик стрелок по сетке 7x6». Но общую концепцию доступности модель не схватывает. Это подтверждает и мой предыдущий эксперимент с сравнением подхода 80/20 и системного проектирования — там я подробно разобрал, почему агентный подход не спас.

Если вам интересно, как AI-агенты, вроде того, что делает Google NAI, справляются с автоматической доступностью — я бы сказал, что там та же проблема: они корректируют очевидное, но не глубокое. Для настоящей a11y пока нужен человек.

🔗
Для тех, кто хочет проверить свой DatePicker на соответствие WCAG, рекомендую использовать NVDA в связке с плагином axe. И не забудьте про axe DevTools — бесплатный инструмент, который выявил 80% моих проблем.

И последнее: не верьте, что AI сделает всё за вас. Доступность — это та область, где автоматизация пока бессильна. Потому что она требует понимания пользовательского опыта людей, которые видят и слышат иначе. А этого у нейросетей нет.

Подписаться на канал