Привет, Хабр!

Я во фронтенде уже пятнадцать лет и всегда видел разные примеры кода «с душком». То, что десять лет назад считалось нормальной практикой, сегодня вызывает улыбку. Современная вёрстка не стала исключением.

Мне нравится наблюдать за тем, как меняются подходы к ней. Это не история о том, что раньше всё было плохо, а сейчас хорошо, или наоборот. Просто одни ошибки уходят, а на их место приходят другие.

В этой статье я собрал несколько распространённых ошибок в современной вёрстке, связанных с доступностью интерфейсов. Постарался быть максимально объективным, но окончательные выводы, как всегда, остаются за вами.

Давайте посмотрим, что я вам подготовил.

Дублирование встроенных подсказок скринридера при использовании атрибута aria-label

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

Давайте посмотрим на разметку области навигации.

<body>
  <!-- другие элементы -->
  <div class="_desktopNav_1lj6g_72">
    <nav class="_desktopNav_2kln2_1" aria-label="Основная навигация">
      <!-- здесь элементы навигации -->
    </nav>
  </div>
  <!-- другие элементы -->
</body>

Для элемента nav добавлен атрибут aria-label.

Здесь я сделаю заметку для тех, кто не сталкивался с ним. Атрибут aria-label похож на элемент label, и его цель предоставить дополнительную информацию об элементе. Её в основном понимают и озвучивают скринридеры.

В нашем примере разработчики добавили значение «Основная навигация». По задумке разработчиков скринридеры должны озвучить её так: «Основная навигация». Ошибка заключается в том, что они скажут: «Основная навигация. Навигация».

Так получается из‑за нюанса работы скринридеров. Есть стандарт ARIA in HTML. В нём описаны обозначения для каждого элемента, по которым скринридеры будут озвучивать подсказки пользователю. Например, для элемента nav — это «навигация».

Поэтому в нашем примере достаточно было написать слово «Основная». В этом случае скринридеры произнесут: «Основная. Навигация».

<body>
  <!-- другие элементы -->
  <div class="_desktopNav_1lj6g_72">
    <nav class="_desktopNav_2kln2_1" aria-label="Основная">
      <!-- здесь элементы навигации -->
    </nav>
  </div>
  <!-- другие элементы -->
</body>

Добавление альтернативного текста для элемента img без учёта его пользы для пользователя

Десять лет назад подобные статьи всегда содержали пример, когда разработчики оставляли без описания элемент img. Каждый раз разработчикам говорили: «Добавьте альтернативный текст, чтобы скринидеры озвучили изображение пользователю».

Давайте посмотрим на разметку из реального мира.

<body>
  <!-- другие элементы -->
  <img alt="День из жизни учителя в&nbsp;Калужской области с доходом 160 000 ₽" class="_image_1nsfr_31" data-clickable="false" src="in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg" loading="eager" draggable="false">
  <!-- другие элементы -->
</body>

Мы видим, что разработчики добавили текст «День из жизни учителя в Калужской области с доходом 160 000 ₽» в атрибут alt. Вроде всё отлично.

А теперь давайте посмотрим на заголовок статьи. Он такой же! И поэтому добавленный альтернативный текст создаёт проблему. Для её демонстрации я покажу, как скринридер NVDA обработает такую разметку. Я буду использовать клавиши стрелок для перемещения по элементам.

Я быстро дойду до изображения и услышу: «Графика. День из жизни учителя в Калужской области с доходом 160 000 ₽». Далее я пройду несколько элементов и нажму ещё раз на клавишу вниз. В ответ услышу: «Заголовок уровень 1. День из жизни учителя в Калужской области с доходом 160 000 ₽».

Получилось так, что пользователи услышат одну и туже подсказку. А главное, что альтернативный текст для изображения не добавил какую‑то существенную пользу. Никакой новой информации для пользователя нет.

Чтобы не получилось такой ситуации, разработчик должен корректно понять тип изображений. Напомню, что есть контентные и декоративные. Первые дают пользователю дополнительный контент или смысл, а вторые служат визуальным оформлением.

В нашем примере превью к статье является визуальным оформлением. Изображение не даёт дополнительный смысл, слабо связано с самой статьёй. В этом случае если вы используете элемент img, то лучше оставить атрибут alt пустым. Не надо его удалять, просто оставьте пустым.

<body>
  <!-- другие элементы -->
  <img alt="" class="_image_1nsfr_31" data-clickable="false" src="in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg" loading="eager" draggable="false">
  <!-- другие элементы -->
</body>

Теперь скринридеры никогда не найдут это изображение, и в нашем случае это отлично! Мы убрали ненужный для пользователя элемент. Упростили восприятие информации.

Отсутствие заголовка в качестве главного элемента блока

Сегодня часто приходится верстать карточки. Под этим термином я имею в виду блоки, которые являются самостоятельной единицей контента. Для демонстрации я буду использовать карточку с названием «День из жизни учителя в Калужской области с доходом 160 000 ₽».

Всё стандартно. У карточки указан автор, дата публикации, количество просмотров, заголовок, вступление, превью, количество лайков, комментариев и добавлений в избранное. В разметке тоже есть чёткая структура.

Автор, количество просмотров и категория объединены одним элементом div.

<body>
  <!-- другие элементы -->
  
  <div class="_info_bqzkd_43">
    <div class="_author_bqzkd_56">
      <a href="/user6447832/" class="_author_4q89k_6" data-size="medium" data-secondary-color="false">
        <div class="_avatar_4q89k_27">
          <!-- здесь аватар -->
        </div>
        <div class="_name_4q89k_44" data-bold="true">Аноним </div>
      </a>
    </div>
    <div class="_nowrap_bqzkd_96">
      <time class="_date_bqzkd_61" datetime="2026-07-17">17.07</time>
      <div class="_views_bqzkd_62">
        <div class="_views_1l2tu_1">
          <span class="_icon_1l2tu_8">
            <!-- здесь иконка комментариев -->  
          </span>
          <span class="_counter_1l2tu_19">14K</span>
        </div>
      </div>
    </div>
  </div>  
  
  <!-- другие элементы -->
</body>

Ниже располагается ссылка, в которой находится заголовок.

<body>
  <!-- другие элементы -->
  
  <a class="_link_bqzkd_20" href="/den-iz-zhizni-uchitel-kalujskaya-oblast-160k/">
    <h3 class="_title_bqzkd_101">День из&nbsp;жизни учителя в&nbsp;Калужской области с&nbsp;доходом 160&nbsp;000&nbsp;₽</h3>
  </a>
  
  <!-- другие элементы -->
</body>

Ещё ниже находится элемент p со вступлением.

<body>
  <!-- другие элементы -->
  
  <p class="_paragraph_1w7nq_3" data-grade="large" data-nomargin="true">31-летний герой переехал с&nbsp;женой из&nbsp;Санкт-Петербурга в&nbsp;Калужскую область и&nbsp;сменил профессию: работал в&nbsp;автобизнесе, а&nbsp;стал сельским учителем. Он&nbsp;с&nbsp;супругой выплачивает кредит на&nbsp;машину и&nbsp;квартиру в&nbsp;Питере, планирует накопить на&nbsp;ремонт, но&nbsp;признается, что пока откладывать деньги не&nbsp;получается: все уходит на&nbsp;жизнь. Герою приходится брать подработки, но&nbsp;он&nbsp;рад, что занимается важным делом.</p>
  
  <!-- другие элементы -->
</body>

Под ним находится превью, размеченное элементом picture.

<body>
  <!-- другие элементы -->

  <div class="_bg_bqzkd_144">
    <picture class="_picture_1nsfr_1" data-with-border="false" data-with-rounding="false" data-covered="true" data-stretched="true">
      <source type="image/webp" srcset="https://opis-cdn.tinkoffjournal.ru/mercury/in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg?preset=card_760x350 760w, https://opis-cdn.tinkoffjournal.ru/mercury/in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg?preset=card_760x350_2x 1520w, https://opis-cdn.tinkoffjournal.ru/mercury/in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg?preset=card_760x350_3x 2280w, https://opis-cdn.tinkoffjournal.ru/mercury/in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg 2560w" sizes="(min-width: 760px) and (max-width: 819px) 700px, (min-width: 820px) 760px, 100vw">
      <img class="_image_1nsfr_31" data-clickable="false" alt="День из&nbsp;жизни учителя в&nbsp;Калужской области с&nbsp;доходом 160&nbsp;000&nbsp;₽" loading="lazy" draggable="false" src="https://opis-cdn.tinkoffjournal.ru/mercury/in-out_den-iz-zhizni-uchitel-kalujskaya-oblast-160k.jpg" style="max-width: 100%; max-height: 100%;">
    </picture>
  </div>
  
  <!-- другие элементы -->
</body>

И ниже всех находится элемент div, объединяющий счётчики комментариев и добавлений в избранное.

<body>
  <!-- другие элементы -->

  <div class="_meta_1jn5s_1">
    <button type="button" class="_likes_kg3jq_1" aria-label="Понравилось">
      <!-- здесь иконка -->
      <span class="_counter_kg3jq_38">65</span>
    </button>
    <a href="/den-iz-zhizni-uchitel-kalujskaya-oblast-160k/#comments" target="_self" class="_bubble_1k91c_1" data-hovered="false" data-inherit-color="false" data-highlighted="true" data-large="false" data-inline="false" aria-label="Комментарии">
      <!-- здесь иконка -->
      <span class="_counter_1k91c_55">81</span>
    </a>
    <button type="button" class="_favorites_aafwi_1" aria-label="Добавить в закладки">
      <!-- здесь иконка -->
      <span class="_counter_aafwi_32">30</span>
    </button>
  </div>
  
  <!-- другие элементы -->
</body>

Почему я так подробно остановился на структуре карточки. Если проанализировать дизайн и код, то разработчики сделали свою работу хорошо.

Есть нюанс с элементом a, в который вложен элемент h3. Я бы не стал так делать, но это решение нельзя назваться ошибкой. Стандарт разрешает так делать. Проблема в другом.

С помощью какого элемента вы быстрее всего понимаете смысл этой карточки? Это заголовок «День из жизни учителя в Калужской области с доходом 160 000 ₽». Почему?

Он визуально выделен. У него размер текста больше. Ещё он отображён жирным. Отступы вокруг него тоже помогают его заметить. Другими словами, эти решения дизайнера помогают нам визуально быстрее понять смысл блока.

Пользователи с частичной или тотальной слепотой так сделать не могут. Они с помощью скринридера будут переключаться по элементам в разметке. И до заголовка они дойдут не сразу. Сначала они узнают, кто автор, дату публикации и количество просмотров, то есть менее важную информацию.

Было бы здорово, чтобы самый важный элемент попадался им первым. Чуть менее важный — вторым. Ещё чуть менее важный — третьим. И так далее.

Игнорирование заголовков

Переходим к давно избитой теме. Сколько раз в статьях я говорил: «Пожалуйста, используйте заголовки». Нет, всё равно нахожу сайты, где они не используются, а должны. Что ж, буду повторять!

Элементы h1h2h3h4h5 и h6 используются пользователями скринридера для восприятия структуры страницы и навигации по ней. Если вы их не добавляете на страницу, то шансов у людей понять, что есть на ней, стремительно уменьшается.

Для примера давайте визуально посмотрим на разделы «Сообщество» и «Популярное за неделю». Мы видим заголовки.

Теперь посмотрим, какие заголовки на странице нашёл скринридер NVDA и будут ли в этом списке наши разделы.

В списке заголовков их нет. Почему так произошло? Да, потому что фронтендеры используют элемент div для заголовков.

<body>
  <!-- другие элементы -->
  <div class="_title_1ye9a_16">Сообщество</div>
  <!-- другие элементы -->
</body>

Далее раздела «Популярное за неделю» то же самое.

<body>
  <!-- другие элементы -->
  <div class="_title_1c6n1_15">Популярное за неделю</div>
  <!-- другие элементы -->
</body>

Пожалуйста, не делайте так. Всегда добавляйте заголовки в разделы страницы. Это очень положительно скажется на опыт пользователей скринридера.

Заключение

Давайте подведём итог. В этой статье я рассмотрел следующие ошибки:

  • написание подсказок для атрибута aria-label, не зная, что элементы уже содержат встроенные;

  • использование альтернативного текста для изображений, которым он не требуется;

  • игнорирование элементов h1h2h3h4h5 и h6;

  • расположение заголовка не первым элементом в блоке.

Надеюсь, вы нашли что-то полезное для себя и задумаетесь над своими решениями. Дополнительно мне будет интересно узнать, какие ошибки в вёрстке критичны для вас. Буду ждать их в комментариях.

На этом я прощаюсь. Спасибо за чтение!

P. S. Помогаю больше узнать про CSS и дружелюбные интерфейсы в своих ТГ‑каналах CSS isn't magic и UX + Dev = a11y. Присоединяйтесь. Как вступить, написано в профиле.

© 2026 ООО «МТ ФИНАНС»

Комментарии (0)