Как сообщать скринридеру об изменениях в таблице

Когда пользователь ищет запись, меняет фильтр или запускает обновление, содержимое таблицы может измениться без перемещения фокуса. Видящий пользователь замечает новый счётчик или строку статуса, а скринридер не всегда сообщает об этом сам. Разберём, какие короткие объявления нужны, как связать их с действием и как не превращать каждое обновление данных в непрерывный голосовой поток.
Почему новой таблицы недостаточно
После фильтрации приложение может заменить двадцать строк пятью. Для человека, который смотрит на экран, изменение заметно сразу: пропавшие строки, обновлённый счётчик, текст «Найдено 5 записей». Пользователь скринридера в этот момент может продолжать читать прежнюю позицию и не узнать, что результат изменился. Сам факт появления нового содержимого не во всех ситуациях сообщает, почему оно появилось и сколько записей теперь доступно.
В WCAG 2.2 критерий 4.1.3 касается статусных сообщений, которые появляются без смены контекста и фокуса: например, результата действия, ожидания, прогресса или ошибки. Руководство W3C отдельно приводит число результатов поиска и сообщение об отсутствии результатов как возможные статусные сообщения. Сам список результатов при этом не заменяется объявлением: статус дополняет таблицу, а не пересказывает её целиком. Подробности есть в разъяснении W3C о статусных сообщениях.
Сначала определите, что именно изменилось
Перед добавлением live region составьте карту действий таблицы. Ищите отдельные события: поиск завершён, фильтр применён, сортировка изменила порядок, сервер вернул новые записи, выбранная строка удалена, массовое действие завершилось. Для каждого события решите, что должен знать пользователь, чтобы продолжить работу. Обычно ответ помещается в одно короткое предложение: количество найденных записей, факт сохранения или объяснение ошибки.
- Поиск и фильтр меняют состав выборки: объявите итоговое количество или отсутствие совпадений.
- Сортировка меняет порядок: назовите столбец и направление, если интерфейс не сообщает их другим понятным способом.
- Обновление данных меняет состояние списка: сообщите, что обновление завершилось, и при необходимости назовите число записей.
- Массовая команда меняет записи: сообщите результат действия и число обработанных строк.
- Ошибка запроса требует объяснения и следующего шага, например повторить загрузку.
Используйте статус для спокойных обновлений
Для обычного подтверждения подходит роль status. В WAI-ARIA у неё предусмотрено вежливое объявление при удобной паузе, без обязательного перехвата фокуса; это поведение подходит для результата поиска или завершения фонового обновления. W3C указывает, что у role="status" есть неявные aria-live="polite" и aria-atomic="true". В реальных сочетаниях браузера и вспомогательной технологии поведение может отличаться, поэтому полезно проверять живой пример с целевой технологией.
- Создайте один постоянный контейнер состояния рядом с таблицей; не создавайте новый узел только после действия.
- Вставляйте в него итоговое сообщение, когда операция завершена.
- Оставьте фокус на поле поиска, кнопке или строке, где пользователь продолжает работать.
- Не дублируйте роль и live-настройки на каждом ряду и каждой ячейке.
Например, контейнер может заранее присутствовать в документе как элемент с role="status" и aria-atomic="true". После запроса в него попадает фраза «Поиск завершён. Найдено 12 записей». W3C отмечает, что явный aria-atomic="true" может быть полезен, если нужно объявлять содержимое контейнера целиком, поскольку в некоторых средах значение по умолчанию обрабатывается не одинаково. Это рекомендация к проверке, а не гарантия одинаковой озвучки во всех комбинациях.
Пишите так, чтобы сообщение отвечало на вопрос пользователя
Хорошая фраза называет завершившееся действие и его результат. «Готово» слишком неопределённо: непонятно, что завершилось. «Найдено 12 записей» полезнее, но при нескольких таблицах контекст может потеряться. «Поиск завершён. Найдено 12 записей» связывает итог с причиной. Для фильтра можно назвать его значение: «Фильтр “В работе” применён. 8 записей». Текст статуса должен совпадать по смыслу с видимым состоянием страницы.
При нулевом результате скажите не только число, но и что делать дальше, если действие очевидно. Например: «По запросу “Север” совпадений нет. Измените запрос или сбросьте фильтры». Это короткое сообщение не заменяет оформление пустого состояния внутри таблицы: зрячему пользователю тоже нужно увидеть причину и возможное действие. Не сообщайте читателю подсказку, если интерфейс не предлагает соответствующий контроль.
При сортировке достаточно подтвердить активный столбец и порядок: «Сортировка по дате, сначала новые». При этом состояние кнопки или заголовка таблицы должно оставаться доступным при навигации. Статус помогает услышать факт изменения в момент нажатия, а семантика элемента помогает позднее проверить текущий порядок. Эти способы дополняют друг друга и не должны расходиться.
Различайте ожидание, результат и ошибку
Во время долгого запроса уместно показать и сообщить «Обновляем таблицу». После ответа замените это сообщение итогом. Если операция быстрая и длится мгновение, промежуточное «Загрузка» может создавать больше шума, чем пользы. Для повторяющегося автообновления обычно важен только результат, который меняет решение пользователя: например, новая запись появилась или обработка завершилась.
Не используйте срочное прерывание для обычной сортировки, поиска или успешного сохранения. Роль alert и aria-live="assertive" предназначены для действительно срочных сообщений: такое объявление может прервать текущую речь и сбросить часть очереди. Ошибка, которую нужно исправить до продолжения формы, может требовать срочного внимания; временный сетевой сбой в фоновой таблице чаще лучше сообщить спокойно, сохранив введённый запрос и доступную кнопку повтора.
Если несколько изменений приходят подряд, объединяйте их в итог одного действия. Например, не объявляйте отдельно «фильтр выбран», «запрос отправлен», «получен ответ», «таблица обновлена», если пользователь совершил одно действие. Сообщите завершённый результат один раз. Для частых автоподгрузок определите понятный интервал и произносите только значимые итоги, иначе объявление будет мешать читать строки.
Сохраняйте понятный порядок действий
Уведомление не должно заставлять пользователя искать новую позицию. После фильтрации фокус часто остаётся в поле ввода или на элементе управления, запустившем фильтр. После удаления строки следует продумать, куда перейдёт фокус, если активная строка исчезла. Это вопрос управления фокусом, отдельный от объявления статуса: фраза может подтвердить удаление, но не должна маскировать потерю текущей позиции.
Когда действие открывает диалог или явно переводит пользователя в другой контекст, это уже другой сценарий. Если фокус перемещён на новый контент и изменение очевидно из нового контекста, дополнительное статусное объявление может повторить то же самое. Проверьте путь целиком: команда, сообщение, новое состояние таблицы и доступный следующий шаг.
Проверьте реализацию вручную
- Запустите поиск с несколькими совпадениями и проверьте озвучивание числа результатов.
- Повторите запрос без совпадений: объявляется причина и не теряется возможность изменить поиск.
- Отсортируйте таблицу в обе стороны: в сообщении понятны поле и направление.
- Проверьте долгий ответ сервера, ошибку и повтор: текст не дублируется и отражает итог.
- Убедитесь, что фокус остаётся в ожидаемом месте и таблица доступна для дальнейшего чтения.
- Проверьте работу хотя бы в целевых сочетаниях браузера и скринридера; одно успешное сочетание не доказывает одинаковую поддержку в остальных.
Учитывайте ввод с задержкой
Если фильтр срабатывает при каждом вводимом символе, не озвучивайте промежуточное число результатов после каждой буквы. Пользователь ещё формирует запрос, а поток сообщений будет перебивать его ввод и создавать впечатление, что система говорит непрерывно. Обновляйте таблицу после небольшой паузы, явной команды поиска или другого принятого в продукте момента завершения ввода. В статус помещайте только итог последнего запроса. Если старые ответы сервера приходят позже новых, не позволяйте им перезаписывать и озвучивать устаревшее количество строк.
Проверяйте не только наличие role="status" в разметке, но и то, что контейнер существует до первого изменения и его текст действительно меняется после действия. Если при повторном одинаковом поиске DOM не меняется, повторное сообщение может не прозвучать. Тогда обновляйте содержимое осознанно и тестируйте это поведение в реальных средах, не добавляя искусственные паузы без необходимости.
Частые ошибки
- Объявлять каждую строку таблицы после фильтрации: это перегружает речь и заставляет повторно слушать данные.
- Использовать одно слово «Готово»: оно не называет действие и не помогает понять итог.
- Ставить aria-live="assertive" на всю таблицу: частые обновления будут перебивать чтение.
- Удалять статусный контейнер и создавать заново при каждом запросе вместо обновления его содержимого.
- Объявлять сообщение, которое противоречит визуальному счётчику или фактическому набору строк.
- Считать подсказку достаточной, если фокус переместился неожиданно или результат нельзя исследовать клавиатурой.
Чеклист перед выпуском
- Для каждого действия с данными определён один полезный итог.
- Спокойные изменения озвучиваются без автоматического переноса фокуса.
- Сообщение короткое, называет действие и результат, при необходимости следующий шаг.
- Строки остаются самостоятельной таблицей, которую можно читать и обходить.
- Уведомления не повторяются чаще, чем меняется значимое состояние.
- Проверены результат, нулевой результат, ошибка, повторный запрос и обновление.
Часто задаваемые вопросы
Нужно ли объявлять каждую строку, загруженную после поиска?
Обычно нет. Объявите итог поиска и количество совпадений, затем оставьте пользователю возможность перейти к строкам обычной навигацией. Озвучивание каждой строки превращает краткий статус в длинный поток и затрудняет возвращение к элементу, с которого началось действие.
Подходит ли role="status" для результата фильтра?
Да, если фильтр обновляет данные без смены контекста и пользователю нужно узнать результат действия. Сообщение вроде «Фильтр применён. Найдено 8 записей» информативнее фразы «Готово». Проверьте его в целевых браузерах и скринридерах.
Нужно ли вручную добавлять aria-live="polite" к role="status"?
По WAI-ARIA у роли status есть неявное значение aria-live="polite". W3C также предупреждает, что в некоторых средах atomic-поведение может различаться, поэтому aria-atomic="true" иногда задают явно. Итог следует подтвердить ручной проверкой, а не предполагать по одному атрибуту.
Когда использовать alert вместо status?
Используйте alert только для сообщения, которое требует немедленного внимания, например критической ошибки, блокирующей текущую задачу. Уведомления assertive могут прерывать озвучивание. Результат обычного поиска, сортировки или обновления лучше сообщать спокойно.
Должно ли сообщение перемещать фокус в таблицу?
Не обязательно. Статус может быть объявлен без получения фокуса; пользователь продолжает работу с поля поиска или кнопки. Перемещение фокуса проектируют отдельно и только когда оно помогает следующему шагу, например после удаления активной строки.
Что сказать, если совпадений нет?
Назовите отсутствие результатов и, если это полезно, доступное действие: «Совпадений нет. Измените запрос или сбросьте фильтры». Не предлагайте действие, которого нет в интерфейсе, и синхронизируйте фразу с видимым пустым состоянием.
Где проверить требования и примеры?
Начните с разъяснения W3C по WCAG 4.1.3, примера role=status для результатов поиска и определения роли status в спецификации WAI-ARIA. Эти документы объясняют критерии и семантику, но не заменяют тестирование вашего интерфейса.
Итог
Для таблицы достаточно нескольких точных сообщений: что завершилось, сколько записей найдено и что делать при ошибке или пустом результате. Держите уведомление коротким, сохраняйте контроль фокуса и проверяйте фактическую озвучку вместе с чтением самой таблицы. Если вы создаёте собственный шрифт для чисел, статусов и служебных экранов, начните с шаблонов Fontgenerator на fontgenerator.ru.