Разберём страницу оплаты: сначала появляется текст «Оплата подтверждена», затем — ссылка на закрытый Telegram-канал. Визуально всё работает. Но пользователь экранного диктора может не узнать об изменениях: фокус остался на кнопке, а обновлённый текст находится в другой части страницы.
Для таких сообщений подходит role="status". Эта роль задаёт live region с неявным aria-live="polite": сообщение может быть объявлено без переноса фокуса, когда скринридер сможет сделать это без прерывания текущего действия. Контейнер должен присутствовать на странице и иметь нужную роль до обновления текста. Рекомендации W3C
Сообщение о состоянии и ссылку лучше разделить:
Перейти в Telegram-канал
У role="status" уже есть неявное aria-atomic="true". Здесь атрибут указан явно: W3C рекомендует это из-за различий поддержки в некоторых окружениях. Он указывает, что при изменении следует представить сообщение целиком. Описание техники ARIA22
Когда сервер возвращает новое состояние заказа, обновляем интерфейс. Этот код должен выполняться после создания элементов — например, из скрипта, подключённого с defer:
const statusNode = document.querySelector("#payment-status");const linkNode = document.querySelector("#channel-link");function renderPayment({ paid, inviteUrl }) { const ready = paid && Boolean(inviteUrl); const message = !paid ? "Ожидаем подтверждения оплаты." : ready ? "Ссылка для входа готова. Она находится ниже." : "Оплата подтверждена. Готовим ссылку на канал."; if (ready) { linkNode.href = inviteUrl; linkNode.hidden = false; } else { linkNode.hidden = true; linkNode.removeAttribute("href"); } if (statusNode.textContent !== message) { statusNode.textContent = message; }}
Это фрагмент отображения трёх состояний: ожидаем оплату, оплата подтверждена, ссылка готова. paid — булево значение, inviteUrl — проверенная сервером ссылка или null. Функция получает актуальное состояние одного заказа от своего бэкенда. Сам возврат с платёжной страницы подтверждением оплаты не считается.
Отказы, возвраты и ошибки запроса здесь не показаны. В рабочем интерфейсе для них нужны отдельные состояния: ошибка получения статуса не означает, что платёж не прошёл.
Контейнер статуса остаётся в DOM. При повторном ответе сервера с тем же состоянием его текст не перезаписывается. Это убирает ненужные изменения live region; поведение озвучивания всё равно нужно проверять в выбранной связке браузера и скринридера.
Ссылка находится вне live region. Статус сообщает о результате, ссылка позволяет перейти к следующему действию. При её появлении код не перемещает фокус: пользователь продолжает работать там, где находился.
Для ручной проверки нужно разнести изменения во времени:
-
Включить скринридер и оставить фокус на кнопке проверки оплаты.
-
Передать состояние
paid: true, inviteUrl: null. -
После объявления сообщения передать состояние с готовой ссылкой.
-
Проверить, что новое сообщение доступно без переноса фокуса.
-
Несколько раз передать тот же ответ и проверить отсутствие лишних объявлений.
-
Перейти к ссылке клавишей Tab и открыть её.
Если два состояния сменяются почти одновременно, не стоит рассчитывать, что скринридер обязательно произнесёт каждое промежуточное сообщение. Для этого сценария важно, чтобы пользователь узнал о готовой ссылке и мог до неё добраться.
В платёжном интерфейсе недостаточно обновить текст на экране. Изменение должно быть доступно пользователю, а следующий шаг — достижим с клавиатуры.
Источник: habr.com