Ревью вёрстки: чек-лист замечаний из практики
Что искать в pull request с версткой: доступность, адаптив, семантика, специфичность CSS. Конкретные формулировки замечаний вместо «поправьте, тут некрасиво».

Ревью верстки чаще всего сводится к «вроде выглядит нормально, апрувлю» — и через месяц выясняется, что кнопка недоступна с клавиатуры, макет ломается на 360px, а перекрытие z-index лечили значением 9999. Ниже — чек-лист, по которому я прохожу каждый PR с HTML и CSS: что смотреть, какими словами это писать в комментарии и почему это не придирка. Формулировки замечаний тоже приведены — «обратите внимание на качество» в ревью не работает.
Материал про то, что ревьюить в готовой верстке, а не как ее писать с нуля. Настройка линтеров и pre-commit-хуков — отдельная тема, здесь только ручное чтение диффа.
С чего начинаю: смотрю HTML до CSS
Первое, что открываю в диффе, — разметку. Если структура кривая, замечания по стилям бессмысленны: их придется переписывать вместе с тегами. Проверяю три вещи.
Семантика вместо div-супа
Блок навигации в <div class="nav">, заголовок в <div class="title">, кликабельная область в <div onclick> — три частых кандидата на замечание. Скринридер и поисковый робот не видят классы, они видят теги.
Формулировка замечания, которая проходит без спора:
Тут<div class="menu">с ссылками внутри — замени обертку на<nav>, а сам список на<ul>/<li>. Скринридер тогда объявит это как навигацию и даст перейти по пунктам, сейчас он читает сплошной текст.
Отдельно ловлю <div onclick> и <span onclick> вместо <button>. Такой элемент не получает фокус с клавиатуры, не реагирует на Enter и Space, не объявляется как кнопка. Лечится заменой на настоящий <button type="button">, а не добавлением tabindex и трех обработчиков.
Один h1 и порядок заголовков
Смотрю иерархию: один <h1> на страницу, без скачков с h2 на h4. Уровень заголовка выбирают не по размеру шрифта — размер задается стилями. Частый антипаттерн: <h3> взяли, потому что «h2 слишком большой». Правильно — оставить h2 и уменьшить его в CSS.
Alt у изображений и подписи у полей
- Информативная картинка —
altописывает содержимое:alt="График роста выручки за 2024 год". - Декоративная картинка — пустой
alt="", чтобы скринридер ее пропустил, а не читал имя файла. - Поле формы — связано с
<label for>или обернуто в него. Плейсхолдер лейбл не заменяет: он исчезает при вводе и не читается частью скринридеров.
Доступность: то, что чаще всего забывают
Доступность (a11y)1 проваливают не из злого умысла, а потому что мышью и на своем мониторе все работает. Прогоняю верстку по клавиатуре и через инструменты.
Фокус виден и логичен
Жму Tab и смотрю: куда прыгает фокус, видна ли обводка. Красный флаг — outline: none без замены на кастомный :focus-visible. Формулировка:
Убралиoutlineу ссылок, но не добавили видимый фокус. Пользователь клавиатуры перестает понимать, где он на странице. Верни индикацию через:focus-visible— например обводкой или тенью.
Контраст текста проверяю через панель разработчика в Chrome (вкладка Elements показывает соотношение контраста в пикере цвета) или через axe DevTools. WCAG AA требует контраста 4.5:1 для обычного текста и 3:1 для крупного — точные пороги смотрите в спецификации WCAG, версии 2.1 и 2.2 отличаются в деталях.
Интерактив доступен без мыши
Кастомный дропдаун, модалка, аккордеон — прохожу с клавиатуры. Модалка должна ловить фокус внутри (focus trap), закрываться по Escape и возвращать фокус на элемент, который ее открыл. Если этого нет — замечание с конкретикой, а не «доработайте модалку».
CSS: специфичность и то, что ломается позже
Здесь ищу не «некрасивый код», а решения, которые взорвутся при следующей правке.
!important и специфичность 9999
Каждый !important в диффе — повод спросить, почему стиль не переопределился обычным способом. Обычно причина — слишком специфичный селектор выше по каскаду. Аналогично z-index: 9999: он не решает проблему наложения, а откладывает ее до появления элемента с z-index: 10000.
Здесь!importantперебивает правило из.card .title. Давай понизим специфичность того селектора до одного класса.card__title, тогда переопределение сработает без!importantи следующему человеку не придется его наследовать.
Магические числа и отсутствие единиц системы
Отступ margin-top: 37px посреди сетки с шагом 8px — либо опечатка, либо у элемента другое выравнивание, которое стоит вынести в комментарий. Не блокирую из-за этого PR, но помечаю: числа вне системы отступов накапливаются и превращают правку макета в разминирование.
Единицы: где px, где rem
| Свойство | Чаще уместно | Почему |
|---|---|---|
| font-size | rem | масштабируется от настроек шрифта пользователя |
| отступы, размеры блоков | rem или px | по договоренности в проекте, главное — единообразно |
| border, тонкие линии | px | 1px не должен зависеть от масштаба шрифта |
| media-запросы | em/px | em надежнее при зумe страницы в части браузеров |
Адаптив: проверяю крайние ширины
Смотрю не только «десктоп и мобилка», а именно узкие места. Открываю DevTools и тяну ширину.
- 360px — реальная ширина бюджетного Android. Проверяю горизонтальный скролл: если появился, ищу блок с фиксированной шириной в px или длинное слово без
overflow-wrap. - Точки перелома макета — двигаю ползунок медленно и смотрю, где верстка «дергается»: наезжают ли элементы друг на друга до срабатывания media-запроса.
- Крупный контент — длинный заголовок, email без пробелов, большое число. Ломает ли он контейнер.
- Зум 200% — WCAG требует работоспособности при увеличении. Часто на этом отваливаются фиксированные высоты и
overflow: hidden.
Отдельно проверяю единицу vh на мобильных: 100vh исторически считался без учета адресной строки браузера, из-за чего секция на весь экран уезжала под нижнюю панель. Если критично — обсуждаю с автором использование dvh (динамический viewport), но проверяю поддержку в целевых браузерах проекта по caniuse, а не по памяти.
Что реально писать в комментариях
Замечание без причины и без варианта решения авторы игнорируют или спорят. Рабочая формула — что не так, почему это проблема, что сделать.
Плохое замечание: «тут кривой отступ». Рабочее: «margin 37px выбивается из сетки 8px — похоже на опечатку, поставь 40px, если так и задумано — оставь комментарий почему».
И разделяю уровни: что блокирует мерж (недоступный интерактив, сломанный адаптив, div вместо кнопки), а что — необязательная придирка. Помечаю необязательное префиксом nit: — автор понимает, что это можно не чинить в этом PR.
Мини-чек-лист на один проход
- Семантика: nav, ul, button, h1 — на своих местах, не div-суп.
- Один h1, заголовки без скачков по уровням.
- alt у картинок, label у полей формы.
- Tab проходит по всем интерактивным элементам, фокус виден.
- Модалка: focus trap, Escape, возврат фокуса.
- Контраст текста проходит AA.
- Нет
!importantиz-index: 9999без объяснения. - 360px без горизонтального скролла, зум 200% не ломает макет.
- Длинный контент не рвет контейнеры.
Что настроить, чтобы ревьюить меньше
Половину пунктов из чек-листа снимает автоматика до ревью. Поставьте в проект stylelint для CSS и eslint-plugin-jsx-a11y (если React) или проверку axe в e2e-тестах — тогда на ручное чтение остаются архитектурные вещи, а не пропущенный alt. Следующий шаг — договориться в команде о единицах (rem или px), шаге сетки и правилах z-index и записать это в гайд, чтобы каждое ревью не начиналось с одних и тех же споров.
1 a11y — сокращение слова accessibility (доступность): 11 букв между «a» и «y». Означает пригодность интерфейса для людей с ограничениями — по зрению, моторике, при использовании клавиатуры или скринридера.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: Web Content Accessibility Guidelines (WCAG) — W3C, MDN Web Docs — HTML elements reference
Частые вопросы
Стоит ли блокировать мерж из-за замечаний по доступности?
Чем проверять контраст и доступность, если нет времени на ручной обход?
А если в проекте вся верстка уже на div и переписывать некогда?
100vh больше не проблема на мобильных, ведь есть dvh?
Как писать замечания, чтобы автор не воспринимал их в штыки?
Нужно ли ревьюить верстку, если есть автотесты и линтеры?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.