Семантический HTML, landmarks и доступность
Семантика — это не про то, чтобы заменить div на section ради красивого слова. Это про контракт между разметкой и вспомогательными технологиями: скринридерами, поисковиками, клавиатурной навигацией и будущими инструментами. Правильная разметка позволяет пользователю понять структуру страницы, не глядя на неё.
Содержание
- Глубокий разбор
- Практические примеры
- Типичные ошибки и антипаттерны
- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
Глубокий разбор
Что такое семантический HTML
Семантический HTML — это разметка, в которой элементы передают смысл, а не только визуальное оформление. div и span не несут семантической нагрузки: они нужны для группировки и стилизации. <article>, <nav>, <button>, <time> несут контракт: браузер и вспомогательные технологии знают, что это такое.
Ключевое правило: если можно использовать семантический элемент вместо ARIA-роли — используй элемент. <button> лучше <div role="button" tabindex="0">, потому что он уже включает в себя фокус, активацию по Enter/Space, корректную роль и поддержку disabled-состояния.
Landmarks: навигация по структуре страницы
Landmarks — это ориентиры, по которым пользователи скринридеров перемещаются между крупными областями страницы. В ARIA их шесть основных:
banner—<header>в контексте страницы;navigation—<nav>;main—<main>;complementary—<aside>;contentinfo—<footer>в контексте страницы;search— явная ARIA-рольsearch, потому что у HTML нет семантического тега для поиска.
<body>
<header>
<a href="/">Логотип</a>
<nav aria-label="Главное меню">
<a href="/about">О нас</a>
<a href="/contacts">Контакты</a>
</nav>
</header>
<main>
<article>
<h1>Заголовок статьи</h1>
<p>Текст статьи...</p>
</article>
</main>
<aside aria-label="Похожие материалы">
<h2>Читайте также</h2>
</aside>
<footer>
<p>© 2026</p>
</footer>
</body>
Важно: header и footer дают роли banner/contentinfo только на уровне страницы, то есть когда они являются прямыми потомками body. Внутри <article> или <section> они остаются просто структурными элементами без landmark-ролей.
Default ARIA roles: когда роль уже встроена
Большинство семантических HTML-элементов имеют встроенные роли. Эти роли работают автоматически, и их не нужно дублировать через role:
| HTML | Default role |
|---|---|
<button> | button |
<a href="..."> | link |
<nav> | navigation |
<main> | main |
<article> | article |
<aside> | complementary |
<h1>–<h6> | heading |
<ul> / <ol> | list |
<li> | listitem |
<table> | table |
Пример избыточной разметки:
<!-- Плохо: роль дублирует семантику тега -->
<nav role="navigation">...</nav>
<button role="button">Отправить</button>
Исключение — когда нужно изменить или доопределить поведение. Например, <button> превращают в role="tab" для вкладок, но при этом разработчик берёт на себя полную ответственность за keyboard-навигацию и состояния.
Заголовки и структура документа
Скринридеры используют заголовки как оглавление страницы. Иерархия должна быть логичной: h1 — один на страницу (обычно), h2 — разделы, h3 — подразделы. Пропуск уровней (h1 → h3) не ломает валидацию, но путает навигацию.
<!-- Хорошо -->
<h1>Руководство по доступности</h1>
<h2>Landmarks</h2>
<h3>Основные ориентиры</h3>
<h3>Дополнительные ориентиры</h3>
<h2>Формы</h2>
<!-- Плохо: пропуск уровня и дублирование h1 -->
<h1>Руководство</h1>
<h3>Landmarks</h3>
<h1>Формы</h1>
Изображения и альтернативный текст
Атрибут alt решает три задачи:
- Описывает содержимое изображения, если оно несёт смысл.
- Скрывает декоративное изображение при
alt="". - Показывает fallback, если картинка не загрузилась.
<!-- Информативное изображение -->
<img src="chart.png" alt="График роста продаж за 2025 год: +23%">
<!-- Декоративное изображение -->
<img src="divider.svg" alt="">
<!-- Плохо: отсутствие alt заставляет скринридер читать имя файла -->
<img src="IMG_1234.jpg">
Отсутствие alt и пустой alt — разные вещи. Пустой alt говорит: “это декоративное изображение, игнорируй”. Отсутствие alt говорит: “разработчик забыл описать”.
Формы и метки
Каждый интерактивный элемент формы должен иметь доступное имя. Лучший способ — связать <label> с полем через атрибут for:
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email" required>
Если визуальная метка не нужна, используй aria-label или aria-labelledby:
<input type="search" aria-label="Поиск по сайту">
<span id="search-label">Поиск</span>
<input type="search" aria-labelledby="search-label">
placeholder не заменяет <label>: он пропадает при вводе и часто имеет недостаточный контраст.
Кнопки и ссылки: разделяй ответственность
<a href="...">— навигация: переход на другую страницу или якорь.<button>— действие внутри страницы: отправка формы, открытие модалки, переключение вкладки.
<!-- Правильно -->
<a href="/about">Подробнее о компании</a>
<button type="button" aria-expanded="false">Показать фильтры</button>
<!-- Неправильно: ссылка без href и с действием вместо навигации -->
<a href="#" onclick="openModal()">Открыть</a>
ARIA: добавлять только когда нужно
ARIA не делает элемент интерактивным автоматически. Если навесить role="button" на <div>, нужно самому реализовать:
tabindex="0"для фокуса;- обработчики
EnterиSpace; - состояния
aria-pressed,aria-expanded; - визуальный фокус.
Поэтому первый вопрос при использовании ARIA: “нельзя ли заменить это нативным HTML-элементом?”
Когда ARIA нужна:
- сложные виджеты: вкладки, аккордеоны, комбобоксы, диалоги;
- динамические уведомления (
role="alert",aria-live); - связи, которые HTML не выражает напрямую (
aria-describedby,aria-controls,aria-owns); - исправление недоступных сторонних компонентов.
<!-- Диалог с корректной ролью и фокус-менеджментом -->
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Подтвердить действие</h2>
<p>Вы уверены?</p>
<button type="button">Отмена</button>
<button type="button">Удалить</button>
</div>
Focus management
Все интерактивные элементы должны быть доступны с клавиатуры. Проверяйте страницу без мыши: Tab для перехода, Shift+Tab для обратного, Enter/Space для активации.
Антипаттерны:
outline: noneбез замены визуального фокуса;<div onclick="...">безtabindexи keyboard-обработчиков;- модальные окна без фокуса внутри и без возврата фокуса при закрытии.
/* Плохо: пользователь теряет понимание, где находится фокус */
button:focus {
outline: none;
}
/* Хорошо: замена outline на видимый стиль */
button:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Практические примеры
Пример 1: карточка статьи
<article>
<header>
<h2><a href="/post/1">Как оптимизировать рендеринг</a></h2>
<p><time datetime="2026-09-03">3 сентября 2026</time></p>
</header>
<p>Краткое описание статьи...</p>
<footer>
<a href="/tag/performance">#performance</a>
</footer>
</article>
<article> создаёт самостоятельный фрагмент. <header> и <footer> внутри него не становятся landmarks, но сохраняют структурное значение.
Пример 2: навигация с выпадающим меню
<nav aria-label="Главное меню">
<ul>
<li><a href="/">Главная</a></li>
<li>
<button type="button" aria-expanded="false" aria-controls="submenu-products">
Продукты
</button>
<ul id="submenu-products" hidden>
<li><a href="/product-a">Продукт A</a></li>
<li><a href="/product-b">Продукт B</a></li>
</ul>
</li>
</ul>
</nav>
Кнопка управляет видимостью подменю. aria-expanded сообщает скринридеру, открыто ли меню. aria-controls связывает кнопку и подменю.
Пример 3: статус операции
<div role="status" aria-live="polite" aria-atomic="true">
Файл успешно загружен
</div>
role="status" — это вежливое уведомление: скринридер произнесёт его, не прерывая текущую речь. Для ошибок используй role="alert" (aria-live="assertive").
Типичные ошибки и антипаттерны
<div>-суп вместо семантики. Если элемент — заголовок, список, кнопка, цитата — используй соответствующий тег.- Дублирование default ARIA roles.
<nav role="navigation">избыточно. - Пропуск
altу информативных изображений. Скринридер начнёт читать URL или имя файла. placeholderвместо<label>. Метка должна быть видна или доступна постоянно.- Ссылки, которые делают действия.
<a href="#" onclick="...">ломает навигацию с клавиатуры и ожидания пользователя. <button>внутри<a>или наоборот. Вложенность интерактивных элементов недопустима: скринридер не поймёт, что активировать.- Отсутствие фокуса в модалках. При открытии диалога фокус должен переходить внутрь, при закрытии — возвращаться к вызвавшему элементу.
- Слишком много ARIA. ARIA — это заплатка, а не замена HTML. Сначала используй нативные элементы.
Ключевые тезисы для интервью
- Семантический HTML важен не сам по себе, а как контракт со скринридерами, поисковиками и клавиатурной навигацией. Нативные элементы (
<button>,<a href>) имеют встроенную семантику, keyboard handling и focus management —<div role="button">требует ручной реализации всего этого. - Landmarks —
<header>,<nav>,<main>,<aside>,<footer>иrole="search"— позволяют пользователям скринридеров быстро перемещаться между смысловыми блоками страницы.<header>и<footer>создают landmarks, только если находятся на верхнем уровне (не вложены в<article>или<section>). - У семантических элементов есть default ARIA roles; дублировать их через
roleне нужно — это избыточно и может запутать вспомогательные технологии. - Пустой
alt=""скрывает декоративное изображение от скринридеров; отсутствие атрибутаalt— ошибка доступности, скринридер зачитает имя файла. - Заголовки (
h1–h6) образуют оглавление документа для скринридера; пропуск уровней (например,h1сразу наh3) и несколькоh1путают навигацию. - ARIA полезна для сложных виджетов, но не делает
<div>полноценной кнопкой автоматически — keyboard handling (Enter, Space), focus management и доступное имя приходится реализовывать вручную. Каждое интерактивное поле формы должно иметь доступное имя через<label>,aria-labelилиaria-labelledby.
Заключение
Семантический HTML — это контракт: браузер и вспомогательные технологии знают, как работать с <button>, <nav>, <main> без дополнительных инструкций. Landmarks позволяют пользователям скринридеров перепрыгивать между смысловыми блоками страницы. ARIA расширяет семантику для сложных виджетов, но является второй линией обороны — после нативных элементов. Доступность начинается с правильной разметки, продолжается в управлении фокусом и заканчивается тестированием реальными пользователями или скринридерами.