Отступы, шрифты и сетка: как собрать страницу сайта по правилам, а не на глаз. Руководство по W3C

Сколько ставить отступов, какой брать кегль и интерлиньяж, как собрать сетку и не сломать её на телефоне. Все цифры — из спецификаций W3C и WCAG 2.2, а не из ощущений. 15 разделов: принципы, сетка, типографика, цвет, компоненты, адаптив, theme.json, доступность, антипаттерны и чек-листы.

Практический справочник для вебмастера. Все нормативные значения — из спецификаций W3C (WCAG 2.2, CSS-модули), переведены и приведены к рабочим числам. Примеры реализации — на Gutenberg / Full Site Editing и theme.json.

Как читать этот документ. Он устроен по слоям — каждый следующий опирается на предыдущий:

  • Разделы 1–5 — система: почему 8 px, откуда шкала шрифтов, что требует WCAG.
  • Разделы 6–8 — готовые значения для кнопок, форм, карточек, секций.
  • Раздел 9 — как это всё вбить в theme.json, чтобы редактор сам подставлял правильные числа.
  • Разделы 12–14 — то, к чему возвращаются: ошибки, чек-листы, сводная таблица.

AA — обязательный уровень WCAG, к нему привязано законодательство большинства стран. AAA — повышенный. Практика — не норматив, а устоявшаяся индустриальная практика.

1. Принципы: почему правила именно такие

Дизайн-система — не про красоту, а про снижение когнитивной нагрузки. Каждое «правило отступов» ниже выводится из одного из шести принципов. Если вы понимаете принцип, вы сможете принять решение в ситуации, которой нет в таблицах.

1.1. Близость (proximity)

Объекты, расположенные ближе друг к другу, воспринимаются как одна группа — до всякого чтения. Это самый мощный инструмент вёрстки и самая частая ошибка новичков.

Рабочее правило: расстояние внутри группы всегда меньше расстояния между группами. Минимум — в два раза.

Близость: подпись под полем формы

Одинаковые отступы

Email
you@site.ru
Телефон
+7 …

Непонятно, лейбл относится к полю сверху или снизу. Глаз делает лишнюю работу.

Внутри 8, между 24

Email
you@site.ru
Телефон
+7 …

Пары «лейбл + поле» читаются мгновенно.

1.2. Выравнивание (alignment)

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

Рабочее правило: количество вертикальных линий выравнивания на экране должно быть минимальным — обычно 2–3 (край контейнера, край колонки, край вложенного блока).

1.3. Повторение (repetition) и единая шкала

Одни и те же величины повторяются по всему сайту. Отступ между секциями всегда 96 px, а не «96, 90, 100 — на глаз». Повторение создаёт ощущение целостности и делает сайт предсказуемым для пользователя, а вёрстку — дешёвой в поддержке.

Отсюда следует необходимость шкалы — конечного набора разрешённых значений (раздел 3). Если значения нет в шкале — его нельзя использовать.

1.4. Контраст и визуальная иерархия

Пользователь не читает страницу — он её сканирует. Иерархия отвечает на вопрос «куда смотреть первым». Строится четырьмя средствами, по силе воздействия:

  1. Размер — самый сильный сигнал. Разница между уровнями должна быть заметной: не 20 и 22 px, а 20 и 32.
  2. Вес и цвет — второстепенный текст делается серым (но с соблюдением контраста, см. 5.2), а не мелким.
  3. Пространство — важный блок окружён воздухом.
  4. Позиция — верх и левый край читаются первыми (для языков с письмом слева направо).
Практическое следствие

На экране должен быть ровно один элемент первого уровня. Если у вас две одинаково крупные кнопки «Купить» и «Узнать больше» — иерархии нет, и конверсия падает у обеих. Вторая кнопка должна быть визуально слабее (outline вместо заливки).

1.5. Закон Фиттса

Время попадания в цель зависит от её размера и расстояния до неё. Отсюда: важные элементы делаются большими и располагаются ближе к «естественному» положению курсора или пальца. Это же требование закреплено нормативно — WCAG 2.2 SC 2.5.8 требует минимум 24×24 CSS-пикселя AA, а SC 2.5.5 рекомендует 44×44 AAA.

1.6. Закон Хика и предел Миллера

Время решения растёт с числом вариантов. Практика: не более 7 пунктов в главном меню, не более 3 карточек в ряд на десктопе для сложного контента, длинные формы делятся на шаги.

1.7. Правило 60–30–10

Пропорция цветов на экране: 60% — фон (обычно белый/светло-серый), 30% — вторичный цвет (текст, блоки, тёмные секции), 10% — акцент (кнопки, ссылки, активные состояния). Акцент, применённый к 40% площади, перестаёт быть акцентом.

2. Сетка, контейнеры, брейкпойнты

2.1. Что такое контейнер и зачем он ограничен

Контейнер — блок, ограничивающий ширину контента по центру экрана. Ограничение нужно по одной причине: слишком длинная строка нечитаема. Глаз теряет начало следующей строки. Норматив: WCAG SC 1.4.8 требует не более 80 символов в строке AAA (40 для иероглифического письма); комфортный диапазон типографики — 45–75 символов.

Тип контейнера Ширина Где применять
Узкий (текстовый) 680–760px / 65ch Статьи блога, документация, юридические страницы
Стандартный 1140–1200px Основной контент, сетки карточек, лендинги
Широкий 1440px Галереи, дашборды, витрины товаров
Full-bleed 100% Фоны секций, hero-изображения, карты
Ключевой приём

Ограничивайте ширину не контейнеру-обёртке, а самому тексту: p { max-width: 68ch; }. Единица ch — ширина символа «0» текущего шрифта, поэтому правило работает автоматически при любом размере шрифта и любой смене гарнитуры.

2.2. Внешние поля (gutters) — отступ от края экрана

Контент никогда не прилипает к краю окна. Минимальные боковые поля:

Экран Боковое поле Комментарий
< 480 px (телефон) 16px Абсолютный минимум — 16. При 12 и меньше текст «вываливается»
480–767 px 20–24px  
768–1023 px (планшет) 32px  
≥ 1024 px 40–64px Дальше работает max-width контейнера
/* Контейнер, который сам справляется со всеми экранами */
.container{
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: clamp(16px, 4vw, 64px);
}

2.3. Колонки и промежутки (gap)

Классика — 12 колонок: делится на 2, 3, 4, 6. Но в CSS Grid жёсткая 12-колоночная сетка чаще всего не нужна — достаточно repeat(auto-fit, minmax()).

Контент Gap по горизонтали Gap по вертикали
Плотная сетка (иконки, теги) 8–16px 8–16px
Карточки товаров / услуг 24px 24–32px
Крупные блоки контента 32–48px 48–64px
Двухколоночный «текст + картинка» 48–64px
/* Сетка карточек без единого media query */
.cards{
  display: grid;
  gap: 24px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

При ширине контейнера меньше 280×2+24 сетка сама схлопнется в одну колонку. Значение 280px — минимальная осмысленная ширина карточки; для карточки с картинкой 3:2 и заголовком это как раз рабочий минимум.

2.4. Брейкпойнты

Брейкпойнт ставится там, где ломается макет, а не там, где выходит новый айфон. Но иметь стандартный набор удобно:

Имя Значение Что меняется
по умолчанию (мобильный) Одна колонка, бургер-меню, вертикальные стеки
sm ≥ 480px Крупные телефоны: 2 колонки в мелких сетках
md ≥ 768px Планшет: 2 колонки, горизонтальные карточки
lg ≥ 1024px Десктоп: полное меню, 3 колонки, sidebar
xl ≥ 1280px Максимальная ширина контейнера, 4 колонки
Mobile-first — это не про моду

Базовые стили пишутся без медиа-запросов и описывают мобильную версию. Всё, что добавляется на больших экранах, идёт в @media (min-width: …). Причина техническая: мобильный CSS получается короче, а «отменять» десктопные стили на телефоне — источник половины багов вёрстки.

2.5. Reflow — норматив адаптивности

WCAG SC 1.4.10 AA: контент должен отображаться без горизонтальной прокрутки при ширине 320 CSS-пикселей (эквивалент 400% зума на экране 1280px) и без вертикальной прокрутки при высоте 256 px для горизонтального контента.

Как проверить за 10 секунд: откройте сайт в браузере на 1280px и нажмите Ctrl/ + + до 400%. Если появилась горизонтальная полоса прокрутки — нарушение. Исключение — контент, требующий двумерного отображения: таблицы данных, карты, схемы, интерфейсы редактирования.

Типичные причины провала: фиксированные width в пикселях, white-space: nowrap на длинных строках, изображения без max-width:100%, таблицы без обёртки со скроллом.

3. Отступы: шкала и логика

3.1. Почему база 8

Причины прагматические, а не мистические:

  • 8 делится на 2 и 4 — при плотности экрана 1.5x и 2x значения остаются целыми пикселями, нет размытия границ;
  • большинство размеров экранов кратны 8 по высоте;
  • конечный набор значений снимает вопрос «16 или 18?» — 18 просто не существует.

Шаг 4 px допускается для мелких внутренних деталей (отступ иконки от текста, padding бейджа), но не для компоновки блоков.

3.2. Рабочая шкала

Шкала отступов
4 · 2xs
8 · xs
12 · sm
16 · md
24 · lg
32 · xl
48 · 2xl
64 · 3xl
96 · 4xl
128 · 5xl
160 · 6xl

Обратите внимание: шкала не линейная, а близкая к геометрической. Причина в законе Вебера — чтобы разница между двумя отступами читалась как разница, она должна быть пропорциональной, а не абсолютной. Разница 4→8 очевидна, 96→100 — незаметна.

:root{
  --space-2xs: 4px;   --space-xs:  8px;   --space-sm: 12px;
  --space-md: 16px;   --space-lg: 24px;   --space-xl: 32px;
  --space-2xl:48px;   --space-3xl:64px;   --space-4xl:96px;
  --space-5xl:128px;  --space-6xl:160px;
}

3.3. Назначение каждой ступени

Значение Типичное применение
4px Зазор между иконкой и текстом внутри кнопки; padding мелкого бейджа
8px Лейбл → поле ввода; элементы внутри одного тега/чипа; gap в группе кнопок
12px Вертикальный padding кнопки среднего размера; отступ между строками списка
16px Между абзацами; padding карточки на мобильном; боковое поле экрана
24px Padding карточки на десктопе; между полями формы; gap сетки карточек
32px Заголовок H2 → следующий блок; между подгруппами внутри секции
48px Между смысловыми блоками внутри секции; padding крупной карточки/модалки
64px Вертикальный padding секции на планшете; отступ до footer
96px Вертикальный padding секции на десктопе — рабочее значение по умолчанию
128–160px Hero-секция, разделение крупных смысловых частей длинного лендинга

3.4. Главное правило: внешние отступы больше внутренних

Прямое следствие принципа близости. Если padding карточки 24, то расстояние между карточками должно быть ≥ 24, а лучше 32. Иначе карточки «слипаются» в единое поле.

Внешние vs внутренние

✕ padding 24, gap 8

Блок A
Блок B

✓ padding 24, gap 32

Блок A
Блок B

3.5. Второе правило: отступ сверху больше отступа снизу

Заголовок принадлежит тексту, который идёт после него, а не до. Поэтому:

h2{ margin-top: 64px; margin-bottom: 16px; }
h3{ margin-top: 40px; margin-bottom: 12px; }
p +  p{ margin-top: 16px; }

Соотношение примерно 3:1 или 4:1. Это единственный способ сделать так, чтобы длинная страница читалась как структура, а не как поток.

3.6. Схлопывание margin — источник багов

Вертикальные margin соседних элементов в обычном потоке схлопываются: margin-bottom: 24px у первого и margin-top: 32px у второго дадут 32, а не 56. Внутри flex- и grid-контейнеров схлопывания нет.

Как жить без сюрпризов

Задавайте вертикальные расстояния через gap в flex/grid-контейнере, а не через margin у детей. Это единообразно, не схлопывается, не требует «убрать margin у последнего элемента».

/* Вместо margin-bottom у каждого блока */
.stack{ display: flex; flex-direction: column; gap: var(--space-lg); }

3.7. Плавные отступы: clamp()

Секция с padding: 96px 0 на телефоне выглядит как дыра. Ставить медиа-запросы на каждый отступ утомительно — используйте clamp(минимум, предпочтительно, максимум):

.section{ padding-block: clamp(48px, 8vw, 96px); }
.hero{    padding-block: clamp(64px, 12vw, 160px); }

Читается так: не меньше 48, не больше 96, между ними — 8% ширины окна. Один раз написали — работает на всех экранах.

Осторожно с clamp() на шрифтах

Если средний аргумент задан только в vw, пользователь не сможет увеличить текст зумом — это нарушение SC 1.4.4 AA (изменение размера текста до 200%). Правильно смешивать rem и vw: clamp(1rem, 0.9rem + 0.5vw, 1.25rem).

3.8. Оптическая коррекция

Математически равные отступы не всегда выглядят равными.

  • Кнопки: текст в кнопке визуально «висит» выше центра из-за отсутствия нижних выносных элементов. Нижний padding иногда делают на 1–2 px больше верхнего.
  • Иконка рядом с текстом: квадратная иконка 24×24 рядом с текстом 16px кажется крупной. Ставьте 20×20 или уменьшайте оптический вес.
  • Круглые элементы (аватары, кружки) требуют чуть больше воздуха, чем прямоугольные того же габарита.
  • Крупные заголовки: из-за большого line-height сверху и снизу образуется «лишний» воздух. Компенсируется отрицательным margin или уменьшением line-height до 1.1.

4. Типографика: размеры и интерлиньяж

4.1. Базовый размер

Базовый размер основного текста — 16–18 px. Ниже 16 не опускаться для основного контента; 14 допустимо для подписей, метаданных, таблиц; 12 — абсолютный минимум для юридических сносок, и то нежелательно.

16 px — дефолт браузера. Если вы пишете html{font-size:16px}, вы просто дублируете дефолт. Если пишете html{font-size:62.5%} ради удобного счёта в rem — вы ломаете настройку размера шрифта, которую пользователь выставил в браузере. Не делайте так.

/* Правильно: базой служит настройка пользователя */
html{ font-size: 100%; }         /* = 16px по умолчанию, но уважает настройки */
body{ font-size: 1.0625rem; }    /* 17px */

4.2. Модульная шкала

Размеры заголовков не выдумываются, а получаются умножением базы на коэффициент. Коэффициент задаёт «характер» сайта:

Коэффициент Название Характер Где уместно
1.125 Большая секунда Сдержанный, плотный Дашборды, админки, тексто­ёмкие сайты
1.200 Малая терция Универсальный Корпоративные сайты, блоги — безопасный выбор
1.250 Большая терция Выразительный Лендинги, услуги
1.333 Кварта Драматичный Промо, портфолио, одностраничники
1.618 Золотое сечение Очень контрастный Обложки, минималистичные промо

4.3. Готовая шкала (база 17px, коэффициент 1.25)

Роль Десктоп Мобильный line-height Вес letter-spacing
Display / Hero 52–64px 32–36px 1.05–1.1 700–800 -0.02em
H1 40–44px 28–30px 1.15 700 -0.02em
H2 32px 24px 1.2 700 -0.015em
H3 26px 20px 1.3 650 -0.01em
H4 21px 18px 1.4 650 0
Lead / подзаголовок 20–22px 18px 1.5 400 0
Body 17px 16–17px 1.6–1.7 400 0
Small / подпись 14–15px 14px 1.5 400 0
Caption / meta 13px 13px 1.4 500 0.01em
Overline / метка 12–13px 12px 1.3 600 0.08em + uppercase
Закономерность, которую стоит запомнить

Чем крупнее шрифт — тем плотнее интерлиньяж и тем сильнее отрицательный трекинг. Крупный текст с line-height:1.6 разваливается на отдельные строки; мелкий с 1.2 — слипается.

4.4. Интерлиньяж (line-height)

Задаётся без единиц: line-height: 1.6, а не 27px. Безразмерное значение наследуется как коэффициент и корректно пересчитывается для вложенных элементов другого размера.

Размер шрифта line-height Почему
≥ 40px 1.0–1.15 Строки и так далеко друг от друга по абсолютной величине
24–39px 1.2–1.3  
17–23px 1.5–1.6  
14–16px 1.5–1.7 Мелкому тексту нужен воздух
Длинная статья 1.7–1.8 Снижает утомляемость при чтении подряд

Норматив: WCAG SC 1.4.8 AAA — интерлиньяж внутри абзаца не менее 1.5, расстояние между абзацами не менее 1.5× интерлиньяжа (то есть ≥ 2.25 от кегля).

4.5. Тест на переопределение интервалов (SC 1.4.12)

Норматив AA, который проваливает огромное число тем WordPress. Пользователь имеет право выставить свои интервалы, и вёрстка не должна ломаться. Проверочные значения:

Свойство Значение теста
line-height не менее 1.5× размера шрифта
отступ после абзаца не менее размера шрифта
letter-spacing не менее 0.12× размера шрифта
word-spacing не менее 0.16× размера шрифта

Вставьте в консоль браузера на своём сайте и посмотрите, что развалится:

document.head.insertAdjacentHTML('beforeend',
`<style>* {
  line-height: 1.5 !important;
  letter-spacing: 0.12em !important;
  word-spacing: 0.16em !important;
}
p { margin-bottom: 2em !important; }</style>`);

Что считается провалом: текст обрезан, текст налезает на соседний блок, текст исчез. Что не считается: просто стало некрасиво.

Типичные причины провала: фиксированная height у кнопок и карточек вместо min-height; overflow:hidden на контейнере с текстом; текст в абсолютно позиционированном блоке фиксированного размера.

4.6. Длина строки

45–75 символов — оптимум, 66 — классический идеал. Меньше 45 — глаз слишком часто переходит на новую строку; больше 80 — теряется начало следующей.

.prose{ max-width: 68ch; }
.prose .lead{ max-width: 58ch; }  /* крупный текст — короче строка */

4.7. Выбор и сочетание шрифтов

  • Одна гарнитура с разными весами — самый безопасный вариант. Две — максимум. Три — почти всегда ошибка.
  • Пара «контраст по классу»: антиква (serif) в заголовках + гротеск (sans) в тексте, или наоборот. Две похожие гротески рядом выглядят как ошибка вёрстки.
  • Веса: достаточно трёх — 400 (текст), 500/600 (акценты, лейблы), 700 (заголовки). Каждый лишний вес — это лишний файл и +20–40 КБ.
  • Кириллица: обязательно проверьте наличие кириллического набора. Много популярных гарнитур его не имеют, и браузер молча подставит системный шрифт.
/* Локальное подключение — быстрее и без сторонних запросов */
@font-face{
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;   /* текст виден сразу системным шрифтом */
  unicode-range: U+0000-00FF, U+0400-04FF;  /* latin + cyrillic */
}

body{
  font-family: 'Inter', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, Arial, sans-serif;
}
Про Google Fonts в WordPress

Подключение шрифтов с fonts.googleapis.com — это внешний запрос, дополнительное DNS-разрешение и, в ряде юрисдикций (в частности, по решениям судов в ЕС по GDPR), передача IP-адреса пользователя третьей стороне. Скачивайте шрифты и кладите в тему. В блочных темах это делается через theme.jsonsettings.typography.fontFamilies.fontFace (см. раздел 9).

4.8. Микротипографика

  • Неразрывный пробел &nbsp; — между числом и единицей (10 кг), после коротких предлогов и союзов, в инициалах.
  • Кавычки: русские «ёлочки» в основном тексте, „лапки“ — вложенные. Не ".
  • Тире: длинное тире (—) в предложении, короткое (–) в числовых диапазонах: 2020–2024.
  • Висячая пунктуация и переносы: hyphens: auto + lang="ru" в теге <html>, иначе переносы не работают.
  • Не растягивайте текст по ширине. text-align: justify без переносов создаёт «реки» из пробелов; WCAG SC 1.4.8 прямо требует не использовать выключку по формату AAA.
  • Не набирайте CAPS длинные фразы — падает скорость чтения. Только короткие метки, и обязательно с letter-spacing: 0.06–0.1em.
  • Табличные цифры в таблицах и ценах: font-variant-numeric: tabular-nums — колонки перестают «прыгать».

5. Цвет и контраст

5.1. Структура палитры

Цвета описываются в два слоя. Сначала «сырые» оттенки, потом семантические токены, ссылающиеся на них. Это позволяет переключить тему в одном месте.

Шкала одного оттенка: 50 → 900
50
100
200
300
400
500
600
700
800
900

Правило: 500 — базовый цвет бренда. 50–100 — фоны и подложки. 600–700 — hover и активное состояние. 800–900 — текст поверх светлых подложек этого же оттенка.

:root{
  /* слой 1 — палитра */
  --blue-50:#eef4ff; --blue-100:#d7e5ff; --blue-500:#1f5fd8;
  --blue-600:#1849a8; --blue-700:#133781; --blue-900:#081737;
  --gray-0:#ffffff;  --gray-50:#f6f7f9;  --gray-200:#e2e5ea;
  --gray-500:#5b6270; --gray-900:#1a1d23;

  /* слой 2 — смысл */
  --color-bg:            var(--gray-0);
  --color-bg-subtle:     var(--gray-50);
  --color-text:          var(--gray-900);
  --color-text-muted:    var(--gray-500);
  --color-border:        var(--gray-200);
  --color-accent:        var(--blue-500);
  --color-accent-hover:  var(--blue-600);
  --color-focus-ring:    var(--blue-500);
}

Полный минимальный набор ролей: bg, bg-subtle, surface (карточки), border, border-strong, text, text-muted, text-inverse, accent, accent-hover, accent-contrast, плюс четыре статусных: success, warning, danger, info — каждый в паре «фон + текст».

5.2. Контраст текста — SC 1.4.3 AA

Что AA AAA
Обычный текст 4.5 : 1 7 : 1
Крупный текст 3 : 1 4.5 : 1
Логотипы, декоративный и отключённый текст требований нет  

«Крупный текст» в терминах WCAG — это ≥ 18 pt (≈ 24 px) обычного начертания или ≥ 14 pt (≈ 18.66 px) полужирного. Заголовок 20 px обычного веса крупным не считается и требует 4.5:1.

Самая частая ошибка на коммерческих сайтах

Светло-серый текст подписей: #999999 на белом даёт 2.85:1 — провал. Минимальный серый, проходящий 4.5:1 на белом, — примерно #767676 (4.54:1). Для крупного текста граница мягче — около #949494 (3.03:1).

5.3. Контраст нетекстовых элементов — SC 1.4.11 AA

Минимум 3:1 к соседнему цвету требуется для:

  • границ полей ввода, чекбоксов, радиокнопок, переключателей;
  • иконок, несущих смысл (иконка «удалить», стрелка аккордеона, иконка в статусе);
  • индикаторов состояния: фокус, выбранная вкладка, активный пункт;
  • значимых частей графиков и диаграмм.

Практическое следствие: модная светло-серая рамка поля ввода #e5e7eb на белом (1.24:1) — нарушение. Граница поля должна быть не светлее примерно #949494.

5.4. Фокус

SC 2.4.7 AA: видимый индикатор фокуса обязателен. SC 2.4.11 AA (новое в WCAG 2.2): сфокусированный элемент не должен быть полностью перекрыт другим контентом — типичная жертва этого правила — липкие шапки, под которые уезжает фокус.

/* Никогда: outline:none без замены */
:focus-visible{
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: 4px;
}
/* липкая шапка + якоря: не даём фокусу уехать под неё */
html{ scroll-padding-top: 88px; }

Рекомендация SC 2.4.13 AAA: контур толщиной не менее 2 CSS-пикселей по периметру и контраст 3:1 между состояниями «в фокусе» и «без фокуса».

5.5. Цвет не может быть единственным носителем информации

SC 1.4.1 A. Красная рамка вокруг поля с ошибкой — недостаточно: нужен текст ошибки и/или иконка. Ссылка внутри абзаца, отличающаяся от текста только цветом, — тоже нарушение, если контраст между цветом ссылки и цветом текста меньше 3:1; подчёркивание решает вопрос.

5.6. Тёмная тема

  • Не используйте чистый чёрный #000 — берите #12141a … #1a1d23. Чистый чёрный с белым текстом даёт эффект «гало» и утомляет.
  • Не используйте чистый белый текст — #e8eaee достаточно.
  • Насыщенные цвета на тёмном «вибрируют» — осветляйте и приглушайте акценты (в шкале берите 300–400 вместо 500–600).
  • Иерархия задаётся не тенями, а светлотой поверхности: чем «выше» слой, тем светлее.
@media (prefers-color-scheme: dark){
  :root{
    --color-bg:#12141a; --color-surface:#1a1d25;
    --color-text:#e8eaee; --color-text-muted:#9aa3b2;
    --color-border:#2b303b; --color-accent:#6fa0ff;
  }
}

6. Компоненты: точные значения

6.1. Кнопки

Размер Высота Padding по X Шрифт Radius Где
Small 32px 12px 14px / 600 6px Таблицы, тулбары, второстепенные действия
Medium 40px 16px 15px / 600 8px Значение по умолчанию
Large 48px 24px 16px / 600 8–10px Формы, основной CTA
XL 56px 32px 17–18px / 650 10–12px Hero, главный CTA лендинга

На мобильном высота кнопки — минимум 44px. Норматив 24×24 AA — это нижняя граница, ниже которой уже нарушение; 44 — комфортный палец.

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  min-height:40px;            /* min-height, не height — иначе сломается на длинном тексте */
  padding:0 16px;
  font:600 15px/1.2 inherit;
  border:1px solid transparent; border-radius:8px;
  cursor:pointer;
  transition: background-color .15s ease, border-color .15s ease;
}
.btn--primary{ background:var(--color-accent); color:#fff; }
.btn--primary:hover{ background:var(--color-accent-hover); }
.btn--primary:active{ transform: translateY(1px); }
.btn--secondary{ background:transparent; color:var(--color-accent);
                 border-color:currentColor; }
.btn:disabled{ opacity:.5; cursor:not-allowed; }
.btn:focus-visible{ outline:3px solid var(--color-focus-ring); outline-offset:2px; }

Обязательные состояния каждой кнопки: default, hover, active (нажатие), focus-visible, disabled, loading. Отсутствие hover/focus — самая частая недоделка.

Расстояние между кнопками в группе: 12–16px. Меньше 8 — риск промаха, больше 24 — кнопки перестают читаться как группа.

6.2. Поля ввода и формы

Параметр Значение
Высота поля 44–48px (мобильный — не менее 44)
Padding внутри 10–12px по вертикали, 12–14px по горизонтали
Размер шрифта в поле 16px минимум — при меньшем iOS Safari принудительно зумит страницу при фокусе
Граница 1px, контраст к фону ≥ 3:1
Radius 6–8px, тот же, что у кнопок
Лейбл → поле 6–8px
Поле → текст ошибки/подсказки 6px
Между полями формы 20–24px
Между группами полей 32–40px
Последнее поле → кнопка submit 32px
Ширина формы 400–480px; поля не растягивать на всю ширину экрана
Плейсхолдер — не лейбл

Текст в placeholder исчезает при вводе, обычно имеет низкий контраст и не читается скринридером как подпись поля. Форма без видимых лейблов нарушает SC 3.3.2 и делает невозможной проверку введённого. Лейбл всегда сверху, всегда видим, всегда связан через for/id.

<div class="field">
  <label for="email">Email</label>
  <input id="email" type="email" autocomplete="email"
         aria-describedby="email-err" aria-invalid="true">
  <p id="email-err" class="field__err">Укажите адрес в формате name@site.ru</p>
</div>

Атрибут autocomplete — не мелочь: SC 1.3.5 AA требует программно обозначать назначение полей, чтобы браузер и вспомогательные технологии могли их автозаполнить.

6.3. Карточки

Параметр Мобильный Десктоп
Padding 16–20px 24–32px
Radius 10–12px 12–16px
Gap между карточками 16px 24–32px
Картинка → заголовок 16px  
Заголовок → описание 8px  
Описание → кнопка/ссылка 16–24px  

Тени. Тень имитирует высоту над плоскостью. Уровни: 0 1px 2px rgba(0,0,0,.06) — едва отделён; 0 4px 12px rgba(0,0,0,.08) — карточка; 0 12px 32px rgba(0,0,0,.12) — модальное окно. Тень не должна быть чёрной и резкой — источник света всегда сверху, значит смещение только по Y.

Radius должен быть согласован. Если у карточки radius 16 и padding 16, то у вложенной картинки radius должен быть 16 − 16 = 0… точнее внешний − padding. Формула: inner = outer − gap. Иначе углы выглядят «неродными».

6.4. Шапка и навигация

Параметр Значение
Высота шапки (десктоп) 72–88px; при скролле можно сжимать до 56–64px
Высота шапки (мобильный) 56–64px
Логотип высота 28–40px
Пункт меню: padding 8px 12px — кликабельная зона ≥ 40px по высоте
Между пунктами меню 4–8px (плюс padding пункта = визуальные 24–32)
Пункт выпадающего меню высота ≥ 40px, padding 10px 16px
Пунктов в главном меню 5–7
Бургер-иконка тач-зона 44×44, сама иконка 24×24
Липкая шапка

На телефоне шапка высотой 88px съедает 13% экрана. Либо уменьшайте её при скролле, либо делайте нелипкой на мобильном, либо прячьте при скролле вниз и показывайте при скролле вверх. И не забудьте scroll-padding-top — иначе якорные ссылки будут заводить заголовок под шапку.

6.5. Иконки

Размер Применение
16px Внутри текста, в мелких кнопках, стрелки в ссылках
20px В кнопках среднего размера, в полях ввода
24px Навигация, тулбары — значение по умолчанию
32–48px Заголовки карточек «преимущества»
64–80px Пустые состояния, крупные фичи

Иконки — только SVG, inline или через спрайт. Иконочные шрифты устарели: они ломаются при отключении шрифтов, плохо кэшируются и читаются скринридерами как случайные символы. Декоративной иконке ставьте aria-hidden="true"; иконке-кнопке без текста — aria-label.

6.6. Таблицы

Параметр Значение
Padding ячейки 10–12px по вертикали, 12–16px по горизонтали
Высота строки 44–52px (компактная — 36)
Шрифт 14–15px
Разделители только горизонтальные, 1px; вертикальные линии почти всегда лишние
Числовые колонки выравнивание вправо + tabular-nums

На мобильном: оборачивайте таблицу в <div style="overflow-x:auto"> с tabindex="0" (чтобы прокрутка была доступна с клавиатуры) — либо перестраивайте в карточки. Таблица данных обязана иметь <th scope="col"> и <caption>.

7. Секции лендинга: рецепты

Ниже — рабочие значения, от которых можно отталкиваться. Все вертикальные отступы даны как clamp(мобильный, fluid, десктопный).

7.1. Универсальная секция

.section{
  padding-block: clamp(56px, 8vw, 96px);
}
.section__head{        /* шапка секции: метка + H2 + лид */
  max-width: 62ch;
  margin-bottom: clamp(32px, 4vw, 56px);
}
.section__label{ font:600 13px/1.3 inherit; letter-spacing:.08em;
                 text-transform:uppercase; color:var(--color-accent);
                 margin-bottom:12px; }
.section__title{ margin:0 0 16px; }
.section__lead{  margin:0; font-size:19px; color:var(--color-text-muted); }

Центрированная шапка секции: margin-inline:auto; text-align:center; — но лид тогда ограничьте 55ch, иначе центрированный текст в 3 строки выглядит рвано.

7.2. Hero (первый экран)

Элемент Значение
Вертикальный padding clamp(64px, 12vw, 160px)
Заголовок clamp(32px, 5.5vw, 60px), line-height 1.05–1.1, max-width 18ch
Заголовок → подзаголовок 20–24px
Подзаголовок 18–22px, max-width 52ch
Подзаголовок → кнопки 32–40px
Между кнопками 12–16px
Кнопки → «социальное доказательство» 40–56px
Про «всё должно влезать в первый экран»

Это миф из 2005 года. Пользователи скроллят. Хуже другое: если первый экран заполнен ровно до нижней границы, у человека не возникает сигнала, что ниже что-то есть. Пусть следующая секция немного «выглядывает» — это надёжнее любой стрелки вниз.

7.3. Сетка преимуществ / услуг

.features{
  display:grid; gap: clamp(24px, 3vw, 40px);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.feature__icon{ width:48px; height:48px; margin-bottom:20px; }
.feature__title{ font-size:20px; margin:0 0 8px; }
.feature__text{ font-size:16px; color:var(--color-text-muted); margin:0; }

Количество в ряд: 3 — для карточек с текстом, 4 — для коротких пунктов с иконкой, 2 — если внутри изображение. Пять в ряд почти всегда читается плохо.

7.4. Блок «текст + изображение»

.media{ display:grid; gap: clamp(32px, 5vw, 64px); align-items:center; }
@media (min-width: 900px){
  .media{ grid-template-columns: 1fr 1fr; }
  .media--reverse > .media__img{ order: -1; }
}
.media__text{ max-width: 52ch; }

Чередуйте стороны (текст слева / текст справа) — это создаёт ритм. Но на мобильном порядок должен быть логичным: сначала текст, потом картинка, а не наоборот. Проверьте, что order не ломает порядок для клавиатуры.

7.5. CTA-секция

Элемент Значение
Padding секции clamp(56px, 8vw, 96px)
Padding внутренней «плашки» clamp(32px, 5vw, 64px)
Заголовок 28–40px, max-width 24ch
Заголовок → текст 16px
Текст → кнопка 32px
Кнопка размер XL, ровно одна главная

7.6. Подвал

Элемент Значение
Верхний padding 64–80px
Нижний padding 32–40px
Между колонками 32–48px
Заголовок колонки → первый пункт 16px
Между пунктами списка 10–12px (тач-зона ≥ 24px по высоте)
Колонки → нижняя строка (копирайт) 40–48px + разделитель 1px
Шрифт 15px, контраст ≥ 4.5:1 даже на тёмном фоне

7.7. Ритм всей страницы

Одинаковый отступ между всеми секциями создаёт монотонность. Управляйте ритмом:

  • Смежные секции одной темы — 64px между ними.
  • Смена смысловой части — 96–128px.
  • Секция с контрастным фоном сама создаёт разделение — её собственный padding можно уменьшить, а внешние отступы убрать в ноль.
  • Не ставьте подряд две секции с цветным фоном — между ними нужна белая «пауза».

8. Адаптив и мобильная версия

8.1. Что именно меняется на брейкпойнтах

Параметр Мобильный Планшет Десктоп
Боковое поле 16px 32px 40–64px
Padding секции по Y 48–56px 64–72px 96px
Колонок в сетке 1 2 3–4
Gap в сетке 16px 24px 24–32px
H1 28–32px 36px 40–44px
H2 24px 28px 32px
Body 16–17px 17px 17px
Padding карточки 16–20px 24px 24–32px
Высота кнопки 48px 44–48px 40–48px
Навигация бургер бургер / сокращённая полная

8.2. Тач-таргеты

Норматив: 24×24 CSS px AA, либо меньший размер, но с достаточным разнесением — вокруг центра каждой малой цели должна умещаться окружность диаметром 24 px, не пересекающая другие цели. Рекомендация AAA и здравый смысл — 44×44.

Исключения из норматива: ссылки внутри текста абзаца; элементы, размер которых задан браузером и не менялся автором; случаи, когда та же функция доступна через другой, крупный элемент.

/* Увеличиваем зону нажатия, не меняя вид иконки */
.icon-btn{
  position:relative; width:24px; height:24px;
}
.icon-btn::after{
  content:''; position:absolute; inset:-10px;   /* 24 + 20 = 44 */
}

Минимальное расстояние между соседними тач-целями — 8 px, комфортное — 12–16.

8.3. Мобильные ловушки

  • Шрифт в поле ввода меньше 16px → iOS Safari зумит страницу при фокусе и не возвращает обратно.
  • 100vh на мобильном учитывает адресную строку и «прыгает» при скролле. Используйте 100dvh (dynamic viewport height) с фолбэком на 100vh.
  • Hover-состояния на телефоне «залипают». Оборачивайте: @media (hover: hover) { .card:hover{…} }.
  • Горизонтальный вылет. Ищите виновника: document.querySelectorAll('*').forEach(e=>{if(e.offsetWidth>document.documentElement.offsetWidth)console.log(e)}).
  • Фиксированные позиции и экранная клавиатура на iOS — «прилипший» к низу CTA перекрывает поле ввода.
  • Безопасные зоны на телефонах с вырезом: padding-bottom: env(safe-area-inset-bottom) для нижних панелей.
  • Ориентация. SC 1.3.4 AA запрещает жёстко ограничивать сайт одной ориентацией экрана.

8.4. Порядок тестирования

  1. 320 px — минимальная ширина по нормативу (iPhone SE 1-го поколения).
  2. 375 / 390 px — реальный массовый телефон.
  3. 768 px — планшет вертикально.
  4. 1024 px — планшет горизонтально / небольшой ноутбук.
  5. 1440 px — типовой десктоп.
  6. 1920 px — проверка, что контейнер не расползся.
  7. Зум 200% и 400% на 1280 px — проверка SC 1.4.4 и 1.4.10.

9. Реализация: theme.json и Gutenberg

Вся система из разделов 3–6 описывается в одном файле theme.json в корне темы. Это не «ещё один способ написать CSS» — это конфигурация, из которой WordPress:

  1. генерирует CSS-переменные вида --wp--preset--spacing--50;
  2. строит интерфейс редактора — селекторы отступов, шрифтов и цветов показывают именно ваши значения;
  3. убирает из интерфейса всё, что вы не разрешили.
Главная мысль раздела

Дизайн-система работает не тогда, когда она описана, а когда контент-менеджер физически не может поставить отступ 37 px. theme.json — это способ сделать неправильное невозможным.

9.1. Каркас файла

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": { … },   /* что РАЗРЕШЕНО и какие пресеты доступны */
  "styles":   { … },   /* что применяется ПО УМОЛЧАНИЮ */
  "templateParts": [ … ],
  "customTemplates": [ … ]
}

Версия 3 — актуальная для WordPress 6.6 и новее. Разница между settings и styles принципиальна: первое наполняет панель редактора, второе задаёт вид «из коробки».

9.2. Отступы: spacingSizes

"settings": {
  "appearanceTools": true,
  "useRootPaddingAwareAlignments": true,
  "layout": {
    "contentSize": "760px",
    "wideSize": "1200px"
  },
  "spacing": {
    "units": ["px","rem","%","vw"],
    "padding": true,
    "margin": true,
    "blockGap": true,
    "customSpacingSize": false,
    "spacingSizes": [
      { "slug": "20", "size": "8px",   "name": "XS · 8" },
      { "slug": "30", "size": "16px",  "name": "S · 16" },
      { "slug": "40", "size": "24px",  "name": "M · 24" },
      { "slug": "50", "size": "32px",  "name": "L · 32" },
      { "slug": "60", "size": "48px",  "name": "XL · 48" },
      { "slug": "70", "size": "clamp(48px, 8vw, 96px)",  "name": "Секция" },
      { "slug": "80", "size": "clamp(64px, 12vw, 160px)", "name": "Hero" }
    ]
  }
}

Что здесь важно по пунктам:

  • "customSpacingSize": false — убирает поле ручного ввода. Теперь доступны только ваши семь ступеней. Это и есть «сделать неправильное невозможным».
  • slug — только цифры, и по возрастанию: WordPress строит из них слайдер. Имена лучше делать говорящими — контент-менеджер видит именно name.
  • В size можно писать clamp() — адаптивный отступ станет доступен в один клик.
  • Переменные наружу: var(--wp--preset--spacing--70).
  • useRootPaddingAwareAlignments: true — позволяет блокам с выравниванием «на всю ширину» выходить за корневой padding, пока контент внутри остаётся в контейнере. Без этого full-width секции будут с полями по краям.

9.3. Типографика с fluid-размерами

"typography": {
  "fluid": true,
  "customFontSize": false,
  "lineHeight": true,
  "letterSpacing": true,
  "fontWeight": true,
  "dropCap": false,
  "fontSizes": [
    { "slug":"small",  "name":"Small · 15",
      "size":"15px" },
    { "slug":"medium", "name":"Body · 17",
      "size":"17px" },
    { "slug":"large",  "name":"H3 · 26",
      "size":"26px",
      "fluid": { "min":"20px", "max":"26px" } },
    { "slug":"x-large","name":"H2 · 32",
      "size":"32px",
      "fluid": { "min":"24px", "max":"32px" } },
    { "slug":"xx-large","name":"H1 · 44",
      "size":"44px",
      "fluid": { "min":"30px", "max":"44px" } },
    { "slug":"display","name":"Hero · 60",
      "size":"60px",
      "fluid": { "min":"34px", "max":"60px" } }
  ],
  "fontFamilies": [
    {
      "slug":"body", "name":"Основной",
      "fontFamily":"Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif",
      "fontFace":[
        { "fontFamily":"Inter", "fontWeight":"100 900", "fontStyle":"normal",
          "fontDisplay":"swap",
          "src":["file:./assets/fonts/inter-var.woff2"] }
      ]
    }
  ]
}

"fluid": true включает автоматический пересчёт: WordPress сам сгенерирует clamp(). Указанные вручную min/max переопределяют автоматику — используйте их для заголовков, где автоматический минимум обычно слишком велик.

"customFontSize": false закрывает произвольный ввод кегля. Спорное решение для блога, обязательное — для корпоративного сайта с несколькими редакторами.

Про file:./ в fontFace

Путь указывается относительно корня темы и работает только начиная с WordPress 6.0. Файлы кладите в assets/fonts/. Проверьте после деплоя, что шрифт реально грузится (вкладка Network, фильтр Font) — опечатка в пути не вызывает никакой ошибки, просто подставляется системный шрифт.

9.4. Цвета

"color": {
  "defaultPalette": false,      /* убрать 12 дефолтных цветов WP */
  "defaultGradients": false,
  "custom": false,              /* запретить пипетку произвольного цвета */
  "customDuotone": false,
  "palette": [
    { "slug":"base",         "name":"Фон",            "color":"#ffffff" },
    { "slug":"base-subtle",  "name":"Фон приглушённый","color":"#f6f7f9" },
    { "slug":"contrast",     "name":"Текст",          "color":"#1a1d23" },
    { "slug":"contrast-muted","name":"Текст вторичный","color":"#5b6270" },
    { "slug":"border",       "name":"Граница",        "color":"#d4d8df" },
    { "slug":"accent",       "name":"Акцент",         "color":"#1f5fd8" },
    { "slug":"accent-dark",  "name":"Акцент тёмный",  "color":"#1849a8" }
  ]
}

Обратите внимание на contrast-muted: #5b6270 — 6.1:1 на белом, запас к нормативу 4.5:1. И на border: #d4d8df: это 1.43:1 к белому, чего недостаточно для границ полей ввода (нужно 3:1). Декоративные разделители так оформлять можно, а для интерактивных границ заведите отдельный, более тёмный токен — например #8a919e (3.17:1).

9.5. Блок styles — значения по умолчанию

"styles": {
  "color": { "background":"var(--wp--preset--color--base)",
             "text":"var(--wp--preset--color--contrast)" },
  "typography": {
    "fontFamily":"var(--wp--preset--font-family--body)",
    "fontSize":"var(--wp--preset--font-size--medium)",
    "lineHeight":"1.65"
  },
  "spacing": {
    "blockGap":"24px",
    "padding": { "left":"var(--wp--preset--spacing--30)",
                 "right":"var(--wp--preset--spacing--30)" }
  },
  "elements": {
    "h1": { "typography":{ "fontSize":"var(--wp--preset--font-size--xx-large)",
                           "lineHeight":"1.15","fontWeight":"700",
                           "letterSpacing":"-0.02em" } },
    "h2": { "typography":{ "fontSize":"var(--wp--preset--font-size--x-large)",
                           "lineHeight":"1.2","fontWeight":"700",
                           "letterSpacing":"-0.015em" } },
    "h3": { "typography":{ "fontSize":"var(--wp--preset--font-size--large)",
                           "lineHeight":"1.3","fontWeight":"650" } },
    "link": {
      "color":{ "text":"var(--wp--preset--color--accent)" },
      "typography":{ "textDecoration":"underline" },
      ":hover": { "color":{ "text":"var(--wp--preset--color--accent-dark)" } },
      ":focus": { "outline":{ "width":"3px","style":"solid",
                              "color":"var(--wp--preset--color--accent)",
                              "offset":"2px" } }
    },
    "button": {
      "color":{ "background":"var(--wp--preset--color--accent)","text":"#ffffff" },
      "spacing":{ "padding":{ "top":"12px","bottom":"12px",
                              "left":"24px","right":"24px" } },
      "border":{ "radius":"8px" },
      "typography":{ "fontSize":"16px","fontWeight":"600","lineHeight":"1.2" },
      ":hover":{ "color":{ "background":"var(--wp--preset--color--accent-dark)" } }
    }
  },
  "blocks": {
    "core/group":  { "spacing":{ "blockGap":"24px" } },
    "core/columns":{ "spacing":{ "blockGap":"32px" } },
    "core/quote":  { "typography":{ "fontSize":"20px","lineHeight":"1.5" } }
  }
}

9.6. Как это ложится на блоки редактора

Задача Блок / приём Куда ставить отступ
Секция во всю ширину с фоном Group → выравнивание Full width, задать Background padding-block = spacing 70 у самого Group
Ограничить контент внутри секции у Group включить Inner blocks use content width
Вертикальный стек с равными зазорами Stack (Group в режиме вертикального flex) blockGap, не margin отдельных блоков
Ряд карточек Columns или Grid (Group → Grid) blockGap у контейнера
Карточка Group с фоном и radius padding = spacing 40, blockGap = spacing 30
Ряд кнопок Buttons blockGap = spacing 20
Пять ошибок, которые убивают систему отступов в Гутенберге

1. Пустой блок Spacer вместо padding у секции. Spacer — это фиксированная высота, которая не адаптируется и не наследует шкалу. Допустим только там, где действительно нужна одноразовая пустота.
2. Margin у отдельных блоков вместо blockGap у контейнера. Приводит к схлопыванию и «лишнему» отступу у последнего элемента.
3. Отступы через «Дополнительный CSS-класс» и правки в Additional CSS. Через полгода никто не найдёт, где задан отступ.
4. Заголовки уровнем не по смыслу, а по размеру — «поставлю H4, потому что нужен мелкий текст». Ломает структуру документа (см. раздел 10).
5. Разрешённый custom spacing. Пока в панели есть поле ручного ввода, в макете будут 37 px.

9.7. Что ещё стоит закрыть в settings

"settings": {
  "border":  { "radius": true, "color": false, "style": false, "width": false },
  "shadow":  { "defaultPresets": false,
               "presets":[
                 {"slug":"sm","name":"Лёгкая","shadow":"0 1px 2px rgba(0,0,0,.06)"},
                 {"slug":"md","name":"Карточка","shadow":"0 4px 12px rgba(0,0,0,.08)"},
                 {"slug":"lg","name":"Модалка","shadow":"0 12px 32px rgba(0,0,0,.12)"}
               ] },
  "background": { "backgroundImage": true, "backgroundSize": true },
  "dimensions": { "minHeight": true, "aspectRatio": true }
}

9.8. Дочерняя тема и порядок переопределения

Приоритет, от низшего к высшему:

  1. Дефолты ядра WordPress (wp-includes/theme.json);
  2. theme.json родительской темы;
  3. theme.json дочерней темы;
  4. Global Styles — то, что редактор сохранил через интерфейс (хранится в БД, в записи типа wp_global_styles);
  5. Стили конкретного блока, заданные в редакторе (inline).
Ловушка при разработке

Если вы правите theme.json, а на сайте ничего не меняется — почти наверняка кто-то уже сохранил настройки через интерфейс Global Styles, и они перекрывают файл. Сбрасывается в редакторе сайта: Styles → меню «⋮» → Reset to defaults. И отключите кэш: define('WP_DEBUG', true) заставляет WordPress перечитывать theme.json при каждом запросе вместо использования кэша.

9.9. Свой CSS, если он всё-таки нужен

/* functions.php дочерней темы */
add_action('wp_enqueue_scripts', function(){
  $css = get_stylesheet_directory() . '/assets/css/custom.css';
  wp_enqueue_style('child-custom',
    get_stylesheet_directory_uri() . '/assets/css/custom.css',
    ['wp-block-library'],
    file_exists($css) ? filemtime($css) : null   /* версия = время правки, кэш не залипает */
  );
});

В самом CSS опирайтесь на сгенерированные переменные, а не на хардкод — тогда смена палитры в theme.json подхватится везде:

.my-card{
  padding: var(--wp--preset--spacing--40);
  border-radius: 12px;
  background: var(--wp--preset--color--base-subtle);
  color: var(--wp--preset--color--contrast);
}

10. Доступность и семантика

Это не «дополнительная опция для незрячих». Семантическая разметка — то, из чего поисковые системы понимают структуру страницы, а браузеры строят режим чтения. Плюс в ЕС с 2025 года действует European Accessibility Act, а в России — ГОСТ Р 52872 для сайтов госорганов и ряда организаций.

10.1. Иерархия заголовков

  • Ровно один <h1> на странице — заголовок содержимого, а не логотип.
  • Уровни не перепрыгиваются: после H2 не может идти H4.
  • Уровень выбирается по вложенности смысла, размер задаётся классом. В Гутенберге: уровень — в настройках блока Heading, размер — отдельным контролем.

Проверка: расширение HeadingsMap или в консоли:

console.log([...document.querySelectorAll('h1,h2,h3,h4,h5,h6')]
  .map(h => '  '.repeat(+h.tagName[1]-1) + h.tagName + ' — ' + h.textContent.trim())
  .join('\n'));

10.2. Ориентиры (landmarks)

<header>   … <nav aria-label="Основное меню">…</nav> …</header>
<main id="content"> … </main>      /* ровно один на странице */
<aside aria-label="Похожие статьи"> … </aside>
<footer> … </footer>

Если на странице два <nav>, каждому нужен свой aria-label — иначе в списке ориентиров они неразличимы.

10.3. Ссылка «к содержимому»

SC 2.4.1 A. Первая фокусируемая ссылка на странице, скрытая до фокуса:

.skip-link{
  position:absolute; left:8px; top:-100px;
  padding:12px 20px; background:#fff; border-radius:8px; z-index:9999;
}
.skip-link:focus{ top:8px; }
Не прячьте скрытое через display:none

display:none и visibility:hidden убирают элемент и из дерева доступности — скринридер его не прочитает и не сфокусирует. Для визуально скрытого, но доступного текста используйте класс .screen-reader-text (он уже есть в WordPress) или clip-path-приём.

10.4. Изображения

Роль изображения Что писать в alt
Несёт информацию То, что изображение сообщает: «График: выручка выросла с 12 до 40 млн за 2021–2024»
Декоративное alt="" — пустой, но обязательно присутствующий атрибут
Внутри ссылки, без текста рядом Куда ведёт ссылка, а не что на картинке
Логотип в шапке Название компании
Сложная схема / инфографика Краткий alt + полное описание в тексте рядом или по ссылке

Не начинайте alt со слов «изображение…» — скринридер и так объявит тип элемента. Не дублируйте в alt подпись, которая уже есть рядом в <figcaption>.

10.5. Клавиатура

  • Всё, что кликабельно мышью, должно быть достижимо через Tab и активируемо через Enter / Space.
  • <div onclick> — не кнопка. Используйте <button>: он бесплатно даёт фокус, обработку клавиш и роль.
  • Порядок фокуса совпадает с визуальным порядком. CSS-свойства order и grid-area порядок фокуса не меняют — это частый источник расхождения.
  • Никаких tabindex больше нуля.
  • Модальные окна: фокус переводится внутрь при открытии, запирается внутри, возвращается на кнопку-инициатор при закрытии, Esc закрывает.

10.6. Прочее, что проверяют аудиты

  • <html lang="ru"> — SC 3.1.1 A. Без этого синтезатор речи прочитает русский текст с английской фонетикой.
  • Уникальный, осмысленный <title> у каждой страницы — SC 2.4.2.
  • Текст ссылки понятен вне контекста: не «подробнее» ×12 на странице, а «подробнее об услуге аудита» (можно скрыть уточнение визуально).
  • Анимации: уважайте @media (prefers-reduced-motion: reduce) — SC 2.3.3.
  • Автопроигрывание видео со звуком дольше 3 секунд требует органов управления — SC 1.4.2.
  • Форма: ошибки описаны текстом, поле связано с сообщением через aria-describedby, фокус переводится к первой ошибке.

11. Изображения, скорость, CLS

11.1. Резервирование места — борьба со «скачками»

Cumulative Layout Shift — метрика Core Web Vitals, штрафующая за смещение контента при загрузке. Целевое значение — менее 0.1. Главная причина скачков — изображения и шрифты без зарезервированного места.

<img src="hero.webp" width="1200" height="800"
     alt="…" loading="lazy" decoding="async">

Атрибуты width и height в HTML обязательны — браузер вычисляет из них соотношение сторон и резервирует место ещё до загрузки файла. Плюс в CSS:

img{ max-width:100%; height:auto; }
.card__img{ aspect-ratio: 3 / 2; object-fit: cover; }
Не ставьте lazy на первый экран

loading="lazy" на hero-изображении откладывает загрузку самого важного элемента и ухудшает LCP. Первому экрану — loading="eager" и fetchpriority="high".

11.2. Размеры и форматы

Назначение Размер файла (px) Формат Целевой вес
Hero, во всю ширину 1920×1080 (2400 для 2x) WebP / AVIF < 200 КБ
Карточка 800×600 WebP < 80 КБ
Миниатюра 400×300 WebP < 40 КБ
Аватар 200×200 WebP < 20 КБ
Логотип, иконки векторный SVG < 10 КБ

WordPress сам генерирует набор размеров и подставляет srcset. Ваша задача — задать корректный sizes, иначе браузер по умолчанию считает, что картинка во всю ширину окна, и грузит слишком большой файл:

add_filter('wp_calculate_image_sizes', function($sizes, $size){
  return '(max-width: 767px) 100vw, (max-width: 1199px) 50vw, 380px';
}, 10, 2);

11.3. Шрифты и скачки текста

font-display: swap показывает текст сразу системным шрифтом и подменяет после загрузки — текст виден, но происходит скачок из-за разной метрики. Смягчается подгонкой фолбэка:

@font-face{
  font-family:'Inter-fallback';
  src: local('Arial');
  size-adjust: 107%;      /* подгоняем ширину под Inter */
  ascent-override: 90%;
  descent-override: 22%;
}
body{ font-family:'Inter','Inter-fallback',Arial,sans-serif; }

И обязательно предзагрузка основного начертания:

<link rel="preload" href="/wp-content/themes/my/assets/fonts/inter-var.woff2"
      as="font" type="font/woff2" crossorigin>

11.4. Бюджет страницы

Метрика Цель
LCP (отрисовка основного контента) < 2.5 с
INP (отклик на взаимодействие) < 200 мс
CLS (смещение макета) < 0.1
Общий вес страницы < 1.5 МБ (лендинг — < 1 МБ)
Число запросов < 50
Файлов шрифтов 1–2 (вариативный — идеально)

12. Антипаттерны: 25 частых ошибок

Отступы и сетка

Ошибка Почему плохо Как правильно
Произвольные значения: 13, 27, 35 px Система не читается, поддержка дорожает Только значения из шкалы (раздел 3.2)
Между карточками зазор меньше их padding Карточки «слипаются», группировка ломается Внешний ≥ внутреннего, лучше ×1.5
Одинаковый отступ до и после заголовка Непонятно, к чему заголовок относится Сверху в 3–4 раза больше
Отступы через <br> или пустые абзацы Не адаптируются, мусорят в разметке margin / gap
Padding секции 96px и на телефоне Пустой экран, лишний скролл clamp(48px, 8vw, 96px)
Контент прилипает к краю экрана Нечитаемо, выглядит как баг Боковое поле минимум 16px

Типографика

Ошибка Почему плохо Как правильно
Основной текст 13–14 px Утомляет, растёт отказ 16–18 px
Строка длиннее 90 символов Глаз теряет следующую строку max-width: 68ch
line-height в пикселях Ломается при наследовании и смене кегля Безразмерное: 1.6
Крупный заголовок с line-height: 1.6 Разваливается на отдельные строки 1.05–1.2 для 40px+
4–5 гарнитур на сайте Визуальный шум + вес страницы 1–2 гарнитуры, 3 веса
Заголовок выбран по размеру, а не по смыслу Рушится структура, страдает SEO и скринридер Уровень — по смыслу, размер — классом
text-align: justify без переносов «Реки» из пробелов, прямой запрет в SC 1.4.8 Выключка влево
Длинный текст капслоком Скорость чтения падает на 10–15% Только короткие метки, + трекинг

Цвет и контраст

Ошибка Почему плохо Как правильно
Серый текст #999 на белом 2.85:1 — провал SC 1.4.3 Не светлее #767676
Граница поля #e5e7eb 1.24:1 — провал SC 1.4.11 Не светлее #949494
outline: none без замены Клавиатурная навигация становится невозможной Свой :focus-visible
Текст поверх фотографии без подложки Контраст непредсказуем Градиентный оверлей или плашка
Ошибка обозначена только красной рамкой Провал SC 1.4.1 + текст ошибки и иконка

Компоненты и поведение

Ошибка Почему плохо Как правильно
Плейсхолдер вместо лейбла Исчезает при вводе, низкий контраст Видимый <label> сверху
Поле ввода с шрифтом 14px iOS Safari зумит страницу при фокусе 16px минимум
Кнопки в мобильном меню высотой 32px Промахи, провал SC 2.5.8 44–48px
Две одинаково яркие кнопки рядом Нет иерархии — падает конверсия обеих Одна primary, одна secondary
Фиксированная height у карточек и кнопок Ломается при длинном тексте и при тесте 1.4.12 min-height
Липкая шапка 88px на телефоне Съедает 13% экрана Сжимать при скролле или прятать

13. Чек-листы приёмки

13.1. Вёрстка и сетка

  • Все отступы — из шкалы; произвольных значений нет
  • Внешние отступы больше внутренних во всех группах
  • Отступ сверху заголовка больше отступа снизу
  • Контент ограничен по ширине; строка не длиннее ~75 символов
  • На 320 px нет горизонтальной прокрутки
  • На 1920 px контейнер не расползся, поля по краям адекватные
  • Секции разделены осмысленно, ритм не монотонный
  • Вертикальные расстояния заданы через gap, а не набором margin

13.2. Типографика

  • Базовый кегль ≥ 16 px, в полях ввода ≥ 16 px
  • Иерархия H1–H4 читается без чтения текста
  • line-height безразмерный и соответствует кеглю
  • Тест SC 1.4.12: с принудительными интервалами ничего не обрезается и не налезает
  • Зум 200% — текст читается, макет цел
  • Кириллица есть во всех подключённых начертаниях
  • Неразрывные пробелы, «ёлочки», длинные тире расставлены

13.3. Цвет и доступность

  • Контраст основного текста ≥ 4.5:1, крупного ≥ 3:1
  • Границы полей, иконки, индикаторы ≥ 3:1
  • Фокус виден на всех интерактивных элементах и не перекрыт липкой шапкой
  • Информация нигде не передаётся только цветом
  • Один H1, уровни заголовков не перепрыгивают
  • Есть <main>, <nav aria-label>, ссылка «к содержимому»
  • У всех изображений есть alt (у декоративных — пустой)
  • <html lang="ru">, у каждой страницы уникальный <title>
  • Весь функционал доступен с клавиатуры; порядок фокуса совпадает с визуальным
  • Тач-цели ≥ 24×24 (лучше 44×44) с зазором ≥ 8 px

13.4. Компоненты

  • У кнопок есть default, hover, active, focus-visible, disabled, loading
  • У полей есть default, focus, error, disabled, заполненное
  • Лейблы видимы и связаны с полями; есть autocomplete
  • Ошибки формы описаны текстом и связаны через aria-describedby
  • Радиусы согласованы: inner = outer − padding
  • Иконки — SVG; декоративные скрыты от скринридера
  • Таблицы на мобильном прокручиваются и доступны с клавиатуры

13.5. Производительность

  • У всех <img> есть width и height
  • Изображения в WebP/AVIF, размеры соответствуют месту показа
  • Первый экран — eager + fetchpriority="high"; остальное — lazy
  • Шрифты локальные, с preload и font-display: swap
  • CLS < 0.1, LCP < 2.5 с (проверено в PageSpeed Insights на мобильном)
  • Нет неиспользуемых плагинов, тянущих свои CSS и JS на каждой странице

13.6. WordPress-специфика

  • theme.json содержит палитру, шкалу отступов и шкалу кеглей
  • customSpacingSize и color.custom отключены
  • Дефолтные палитра и градиенты WordPress отключены
  • Правки живут в дочерней теме, не в Additional CSS
  • Global Styles в БД не конфликтуют с файлом (сброшены после отладки)
  • Секции сделаны через Group + padding, а не через Spacer
  • Расстояния внутри контейнеров заданы через blockGap

14. Шпаргалка: все значения на одной странице

Отступы

Шкала 4 · 8 · 12 · 16 · 24 · 32 · 48 · 64 · 96 · 128 · 160
Секция по вертикали моб. 48–56 / планш. 64–72 / десктоп 96
Боковое поле моб. 16 / планш. 32 / десктоп 40–64
Между карточками 16 / 24 / 24–32
Padding карточки 16–20 / 24 / 24–32
Между абзацами 16
H2: сверху / снизу 64 / 16
Между полями формы 20–24
Лейбл → поле 6–8
Между кнопками 12–16

Типографика

Body 17 px / 1.65 / 400
H1 десктоп 44 · моб. 30 / 1.15 / 700 / −0.02em
H2 32 · 24 / 1.2 / 700 / −0.015em
H3 26 · 20 / 1.3 / 650
H4 21 · 18 / 1.4 / 650
Small 15 / 1.5
Caption 13 / 1.4
Длина строки 45–75 симв., max-width: 68ch
Модульный коэффициент 1.2 (универсально) / 1.25 (лендинг)

Компоненты

Кнопка S / M / L / XL 32 / 40 / 48 / 56 px высота
Поле ввода 44–48 px, шрифт ≥ 16 px
Тач-цель ≥ 24×24 (норматив), 44×44 (практика)
Radius кнопки/поля 6–8 · карточки 12–16 · модалки 16
Иконки 16 · 20 · 24 · 32–48 · 64–80
Шапка десктоп 72–88 · моб. 56–64
Тени 0 1px 2px /.06 · 0 4px 12px /.08 · 0 12px 32px /.12

Нормативы WCAG 2.2

SC Требование Уровень
1.4.1 Информация не только цветом A
1.4.3 Контраст текста 4.5:1 · крупного (≥24px или ≥18.66px bold) 3:1 AA
1.4.4 Масштабирование текста до 200% без потерь AA
1.4.8 ≤80 символов в строке · интерлиньяж ≥1.5 · без justify AAA
1.4.10 Reflow: без гориз. прокрутки при 320 CSS px AA
1.4.11 Контраст нетекстовых элементов 3:1 AA
1.4.12 Интервалы: 1.5 / 2× / 0.12em / 0.16em — без потерь AA
2.4.1 Обход блоков (skip-link) A
2.4.7 Видимый фокус AA
2.4.11 Фокус не перекрыт другим контентом AA
2.4.13 Контур фокуса ≥2 px, контраст состояний 3:1 AAA
2.5.5 Тач-цель 44×44 AAA
2.5.8 Тач-цель 24×24 либо разнесение AA
3.1.1 Язык страницы (lang) A
3.3.2 Подписи и инструкции к полям A

Полезные проверки

Контраст WebAIM Contrast Checker · панель Accessibility в DevTools
Автоаудит axe DevTools · Lighthouse · WAVE
Скорость PageSpeed Insights (смотреть вкладку «Мобильные») · WebPageTest
Структура заголовков HeadingsMap · сниппет из раздела 10.1
Reflow зум 400% на 1280 px
Интервалы сниппет из раздела 4.5
Клавиатура пройти всю страницу только Tab и Enter
Про автоматические аудиты

Автоматические инструменты находят порядка 30–40% проблем доступности. Контраст, отсутствующие alt и lang они поймают. Осмысленность alt, логичность порядка фокуса, работоспособность модалки с клавиатуры — нет. Ручная проверка обязательна.

15. Источники W3C

Все нормативные значения в этом документе взяты из следующих материалов W3C (перевод и адаптация — авторские):

Документация WordPress по theme.json: Theme.json Reference и Global Settings & Styles.


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

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

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *