Svelte

Доступность: a11y-предупреждения компилятора

Разработчик делает кликабельную карточку товара через div с onclick. Визуально работает, мышью открывается. Компилятор Svelte при сборке выдаёт предупреждение: на div повешен обработчик клика, но нет роли и обработки клавиатуры. Предупреждение проигнорировано. Через месяц приходит жалоба: пользователь, который ходит по сайту с клавиатуры, не может открыть ни одну карточку - Tab её не выделяет, Enter не срабатывает. Замена div на button и пара ARIA-атрибутов чинят это, и предупреждение исчезает.

  • Компилятор Svelte - один из немногих, кто проверяет доступность прямо при сборке и предупреждает о проблемах до запуска кода
  • WCAG и спецификация WAI-ARIA - отраслевые стандарты доступности, на которые опираются предупреждения компилятора
  • Государственные и крупные коммерческие сайты обязаны соответствовать требованиям доступности по закону во многих юрисдикциях
  • Скринридеры (VoiceOver, NVDA, JAWS) читают страницу по ролям и подписям, а не по визуальному виду - доступная разметка для них критична
  • Playwright и axe-core - стандартная связка для проверки a11y в браузере поверх статических предупреждений Svelte

Предварительные знания

  • Синтаксис шаблона Svelte: разметка, атрибуты, обработчики событий вида onclick
  • Базовое знание семантического HTML: button, a, label, input
  • Идея о том, что по странице можно перемещаться не только мышью, но и клавиатурой (Tab, Enter, Esc)

Предупреждения компилятора о доступности

Большинство инструментов проверяют доступность во время выполнения или в отдельном линтере. Компилятор Svelte встроил часть проверок прямо в сборку: разбирая разметку, он замечает типичные ошибки и выдаёт предупреждение в консоль и в редактор. Это сдвигает обнаружение проблемы влево - к моменту написания кода, а не к жалобе пользователя.

ПредупреждениеЧто проверяетКак чинить
a11y_click_events_have_key_eventsНа некликабельном элементе клик без клавиатурыИспользовать button или добавить обработку клавиш и роль
a11y_missing_attributeУ img нет alt, у a нет hrefДобавить alt с описанием, href или нужный атрибут
a11y_label_has_associated_controllabel не связан с полемСвязать через for/id или вложить input в label
a11y_no_noninteractive_tabindextabindex на неинтерактивном элементеУбрать tabindex или сделать элемент интерактивным семантически

Предупреждения можно подавить директивой комментария, но это инструмент крайнего случая. Подавление уместно, когда разработчик точно понимает, почему правило в этом месте не применимо. Глушить предупреждение, чтобы оно просто исчезло, означает вернуть проблему доступности обратно в продукт.

В чём ценность того, что Svelte проверяет доступность на этапе компиляции?

ARIA: когда семантики HTML не хватает

ARIA (Accessible Rich Internet Applications) - набор атрибутов, которые сообщают вспомогательным технологиям роль, состояние и свойства элемента. Главное правило, прямо из спецификации: не применять ARIA, если есть подходящий нативный элемент. Нативный button уже несёт роль button, фокусируемость и обработку клавиш. Воссоздавать это на div через role и tabindex - значит писать больше кода ради того, что HTML даёт бесплатно.

Здесь ARIA оправдана: паттерна tabs в HTML нет, поэтому роли tablist, tab, tabpanel и состояние aria-selected описывают скринридеру структуру, которую визуально передают только стили. Атрибут aria-controls связывает вкладку с её панелью. Без этого скринридер увидел бы просто набор кнопок без смысла группировки.

Неверная ARIA хуже её отсутствия. role="button" на div без tabindex и обработки клавиш создаёт элемент, который скринридер объявляет кнопкой, но который не фокусируется и не активируется с клавиатуры. Пользователь слышит обещание, которое интерфейс не выполняет. Нативный button не оставляет шанса на такую рассинхронизацию.

  1. Сначала искать нативный элемент: button, a, input, details, dialog
  2. Если паттерна в HTML нет (tabs, дерево, комбобокс) - брать ARIA по соответствующему паттерну из WAI-ARIA Authoring Practices
  3. Атрибут состояния (aria-selected, aria-expanded) держать в синхроне с реактивным состоянием компонента
  4. Проверять результат скринридером или axe, а не только глазами

Нужна обычная кнопка действия. Какой подход верный?

Управление фокусом в динамических интерфейсах

В статичной странице браузер сам ведёт фокус по элементам. В динамическом интерфейсе этого мало. Когда открывается модальное окно, фокус должен переместиться внутрь него, иначе пользователь с клавиатуры или скринридером остаётся снаружи и не понимает, что произошло. Когда модалка закрывается, фокус возвращается на элемент, который её открыл, чтобы не потерять место на странице.

Перед открытием код запоминает текущий активный элемент через document.activeElement - это и есть триггер. Затем фокус переводится на контейнер диалога; tabindex со значением -1 делает его программно фокусируемым, не вставляя в обычный обход по Tab. При закрытии фокус возвращается на сохранённый триггер. Проверка instanceof HTMLElement - это type guard, который заменяет небезопасный каст.

Нативный элемент dialog с методом showModal() берёт часть этой работы на себя: ловушку фокуса внутри окна, закрытие по Esc и затемнение фона. Если паттерн укладывается в стандартный диалог, dialog предпочтительнее ручной реализации, потому что доступность встроена в платформу.

Что должно происходить с фокусом при закрытии модального окна?

Навигация с клавиатуры

Часть пользователей не использует мышь вообще: из-за моторных особенностей, при работе со скринридером или просто по привычке. Для них интерфейс должен полностью управляться с клавиатуры. Базовые ожидания: Tab перемещает фокус по интерактивным элементам, Enter и Space активируют, Esc закрывает оверлеи, стрелки двигают по группам (вкладки, меню, список радио).

КлавишаОжидаемое действиеГде
Tab / Shift+TabПереход к следующему / предыдущему элементуВезде по интерактивным элементам
EnterАктивация ссылки или кнопкиКнопки, ссылки, отправка формы
SpaceАктивация кнопки, переключение чекбоксаКнопки, чекбоксы
EscapeЗакрытие модалки, меню, подсказкиОверлеи и всплывающие элементы
СтрелкиПеремещение внутри группыВкладки, меню, радиогруппа, слайдер

Видимый индикатор фокуса обязателен. Распространённая ошибка - убрать обводку через outline: none ради эстетики, не дав замены. Пользователь с клавиатуры перестаёт понимать, где находится. Если стандартная обводка не вписывается в дизайн, её заменяют своим стилем :focus-visible, а не убирают совсем.

Нативные элементы снова экономят труд. button и a реагируют на Enter и Space из коробки, отправка формы по Enter работает без обработчиков. Ручная обработка клавиш нужна там, где паттерна нет в HTML: стрелки между вкладками, Esc для закрытия своего попапа. Чем больше опора на семантику, тем меньше ручного кода клавиатуры и меньше шансов на ошибку.

Дизайнер просит убрать обводку фокуса через outline: none, потому что она портит вид. Как поступить?

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

Доступность пронизывает разметку, стили и тесты. Связи:

  • Тестирование — Playwright с axe ловит a11y-проблемы в реальном браузере там, где статический анализ компилятора бессилен
  • Стилизация — Видимый индикатор фокуса и достаточный контраст - часть доступности, решаемая через CSS компонента

Итог

  • Компилятор Svelte проверяет доступность статически и выдаёт предупреждения при сборке - это редкая возможность среди фреймворков
  • Семантический HTML решает большинство проблем бесплатно: button кликается мышью и клавиатурой, получает фокус и роль без единого атрибута
  • ARIA нужна, когда семантики HTML не хватает; первое правило ARIA - не применять ARIA, если есть подходящий нативный элемент
  • Управление фокусом обязательно в динамических интерфейсах: при открытии модалки фокус переводится внутрь, при закрытии возвращается на триггер
  • Навигация с клавиатуры это не опция: Tab для перехода, Enter и Space для активации, Esc для закрытия, видимый индикатор фокуса

Связанные уроки

  • sv-37-testing-vitest-playwright — Playwright с axe проверяет доступность в браузере, дополняя статические предупреждения компилятора во время сборки
  • sv-40-styling-scoped-css-variables — Видимый фокус и контраст - часть доступности, и реализуются они средствами стилизации компонентов
Доступность: a11y-предупреждения компилятора

0

1

Войти