Перейти к основному содержимому
    Назад к блогу
    РазработкаОпубликовано 21 октября 2026 г.

    ARIA и динамический контент: когда атрибуты помогают, а когда вредят

    Первое правило ARIA, типичные ошибки, состояния элементов и live-регионы для уведомлений, загрузки и одностраничных приложений

    ARIA и динамический контент: когда атрибуты помогают, а когда вредят

    Часть 6 из 8 серии «Доступный веб на практике». Предыдущая часть: Контраст, анимация и размер кнопок: визуальная доступность сайта.

    ARIA-атрибутов на сайтах становится всё больше, а ошибок вместе с ними — тоже. В этой части разберёмся, когда ARIA действительно нужна, как её не сломать и как сообщать скринридеру об изменениях на странице.

    ARIA: когда нужна и когда вредит

    WAI-ARIA — набор атрибутов (role, aria-*), которые изменяют то, как элемент представлен в дереве доступности. ARIA ничего не меняет в поведении: role="button" не сделает div фокусируемым и не добавит реакцию на клавиши.

    Первое правило ARIA

    Если можно использовать нативный HTML-элемент или атрибут с нужной семантикой и поведением — используйте его вместо того, чтобы переназначать элемент и добавлять ARIA. — W3C, Using ARIA

    У этого правила есть статистика. В отчёте WebAIM Million 2026 страницы с ARIA содержат в среднем 59,1 ошибки, без ARIA — 42, а среднее число ARIA-атрибутов на страницу превысило 133 и выросло на 27% за год. Это не значит, что ARIA вызывает ошибки сама по себе: она чаще встречается на сложных страницах. Но неправильная ARIA делает хуже, чем её отсутствие, — неверная роль или устаревшее состояние прямо дезинформируют пользователя.

    Частые ошибки:

    <!-- ❌ aria-label на элементе без роли игнорируется или читается непредсказуемо -->
    <div aria-label="Карточка товара">...</div>
    
    <!-- ❌ Дублирование: скринридер прочитает «кнопка» и так -->
    <button role="button">OK</button>
    
    <!-- ❌ aria-hidden на фокусируемом элементе: фокус попадает «в никуда» -->
    <a href="/cart" aria-hidden="true">Корзина</a>
    
    <!-- ❌ aria-label расходится с видимым текстом — ломает голосовое управление -->
    <button aria-label="submit">Отправить заявку</button>
    

    Последний пример нарушает критерий 2.5.3 Label in Name: доступное имя должно содержать видимый текст, желательно начинаться с него.

    Где ARIA действительно нужна

    ARIA незаменима там, где в HTML нет подходящего элемента или нужно передать состояние:

    Раскрывающийся блок (disclosure)

    <button type="button" aria-expanded="false" aria-controls="faq-1">
      Сколько стоит сайт?
    </button>
    <div id="faq-1" hidden>
      <p>Лендинг — от ... Многостраничный сайт — от ...</p>
    </div>
    
    document.querySelectorAll('[aria-controls]').forEach((button) => {
      const panel = document.getElementById(button.getAttribute('aria-controls'));
      button.addEventListener('click', () => {
        const expanded = button.getAttribute('aria-expanded') === 'true';
        button.setAttribute('aria-expanded', String(!expanded));
        panel.hidden = expanded;
      });
    });
    

    Для простых случаев есть нативный вариант без JavaScript — <details> и <summary>:

    <details>
      <summary>Сколько стоит сайт?</summary>
      <p>Лендинг — от ... Многостраничный сайт — от ...</p>
    </details>
    

    Кнопка-переключатель

    <button type="button" aria-pressed="false">Только избранное</button>
    

    Подписи и описания

    • aria-labelledby — имя берётся из другого видимого элемента (заголовок модалки, заголовок секции);
    • aria-describedby — дополнительное описание (подсказка, ошибка);
    • aria-label — имя, когда видимого текста нет (кнопка-иконка).

    Приоритет: видимый текст внутри элемента → aria-labelledby → aria-label. Имена, которые видит пользователь, всегда лучше невидимых.

    Состояния: aria-expanded, aria-pressed, aria-selected, aria-checked, aria-current, aria-invalid, aria-busy, aria-disabled. Главное — обновлять их синхронно с визуальным состоянием. Меню открыто, а aria-expanded="false" — это хуже, чем вообще без атрибута.

    Для сложных виджетов (вкладки, комбобокс, меню, дерево, грид) — ARIA Authoring Practices Guide. Но учтите предупреждение самих авторов: паттерны APG — это демонстрация, а не готовые к продакшену компоненты, и поддержка во вспомогательных технологиях проверяется отдельно.

    Динамический контент и объявления

    В одностраничных приложениях содержимое меняется без перезагрузки: товар добавился в корзину, появилось уведомление, загрузились результаты поиска. Зрячий пользователь это видит, а скринридер — молчит, если ему не сказать.

    Live-регионы

    Класс visually-hidden в примерах ниже скрывает элемент визуально, но оставляет его скринридерам. Его CSS — в части «Семантический HTML и альтернативный текст».

    <!-- Регион должен существовать в DOM ДО изменения текста -->
    <div id="status" role="status" aria-live="polite" class="visually-hidden"></div>
    
    function announce(message) {
      const region = document.getElementById('status');
      region.textContent = '';                 // сброс, чтобы повтор того же текста тоже прочитался
      setTimeout(() => { region.textContent = message; }, 100);
    }
    
    addToCartButton.addEventListener('click', async () => {
      await addToCart(productId);
      announce('Товар добавлен в корзину. В корзине 3 товара.');
    });
    
    Атрибут Поведение Когда
    aria-live="polite" / role="status" Прочитает, когда скринридер закончит текущую фразу Статусы, уведомления, «найдено 12 результатов»
    aria-live="assertive" / role="alert" Прервёт и прочитает сразу Только срочное: ошибка, сессия истекает

    Самая частая ошибка — создать регион вместе с сообщением (например, отрендерить тост с role="alert" в момент события). Многие скринридеры отслеживают изменения только в регионах, которые уже были на странице. Держите один-два пустых региона в разметке с самого начала и меняйте их текст.

    Не злоупотребляйте assertive: постоянные прерывания мешают так же, как всплывающие окна.

    Загрузка

    <section aria-labelledby="results-title" aria-busy="true">
      <h2 id="results-title">Результаты поиска</h2>
      <!-- скелетон -->
    </section>
    

    aria-busy="true" подсказывает, что содержимое обновляется. После загрузки снимите его и объявите результат через live-регион: «Найдено 12 вакансий».

    Тосты и уведомления

    Если уведомление исчезает само, у человека должно хватить времени его прочитать (критерий 2.2.1), а если в нём есть действие («Отменить»), до него нужно иметь возможность добраться клавиатурой. Надёжное правило: уведомления с действиями не исчезают автоматически, а важные действия дублируются вне тоста.

    Бесконечная прокрутка

    Бесконечная лента недоступна до футера для клавиатуры и тяжела для скринридера. Кнопка «Показать ещё» решает обе проблемы и даёт пользователю контроль. После нажатия переведите фокус на первый новый элемент.

    Серия «Доступный веб на практике»

    1. Доступность сайта: для кого она на самом деле и с чего начать
    2. Семантический HTML и альтернативный текст: фундамент доступного сайта
    3. Доступные формы: подписи, подсказки, ошибки и автозаполнение
    4. Клавиатура и фокус: как сделать сайт, которым можно пользоваться без мыши
    5. Контраст, анимация и размер кнопок: визуальная доступность сайта
    6. ARIA и динамический контент: когда атрибуты помогают, а когда вредят — вы здесь
    7. Доступность в React: id, фокус при смене маршрута и объявления
    8. Как тестировать доступность: автоматика, ручная проверка и чеклист

    Следующая часть: Доступность в React: id, фокус при смене маршрута и объявления

    Agama Labs — разработка сайтов и веб-приложений. Если хотите проверить доступность своего сайта, напишите нам: hello@agamalabs.com

    Контакты

    Давайте обсудим ваш проект

    Оставьте заявку, и мы свяжемся с вами в течение 2 часов для бесплатной консультации. Расскажите о вашей идее — поможем найти оптимальное решение.

    Время работы

    Пн–Пт: 08:00 – 17:00 (PST)
    Сб–Вс: Выходной

    Оставить заявку

    Заполните форму, и мы перезвоним вам

    Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности

    Мы ценим вашу конфиденциальность

    Мы используем аналитические cookies, в том числе сторонних сервисов, чтобы понимать, как используется сайт, и улучшать его