Отступы, шрифты и сетка: как собрать страницу сайта по правилам, а не на глаз. Руководство по 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)
Объекты, расположенные ближе друг к другу, воспринимаются как одна группа — до всякого чтения. Это самый мощный инструмент вёрстки и самая частая ошибка новичков.
Рабочее правило: расстояние внутри группы всегда меньше расстояния между группами. Минимум — в два раза.
Одинаковые отступы
Непонятно, лейбл относится к полю сверху или снизу. Глаз делает лишнюю работу.
Внутри 8, между 24
Пары «лейбл + поле» читаются мгновенно.
1.2. Выравнивание (alignment)
Ничего на странице не должно стоять произвольно. Каждый элемент имеет визуальную связь с другим через общую линию. На практике это означает: один левый край для всего контента колонки, и никаких «чуть-чуть подвину».
Рабочее правило: количество вертикальных линий выравнивания на экране должно быть минимальным — обычно 2–3 (край контейнера, край колонки, край вложенного блока).
1.3. Повторение (repetition) и единая шкала
Одни и те же величины повторяются по всему сайту. Отступ между секциями всегда 96 px, а не «96, 90, 100 — на глаз». Повторение создаёт ощущение целостности и делает сайт предсказуемым для пользователя, а вёрстку — дешёвой в поддержке.
Отсюда следует необходимость шкалы — конечного набора разрешённых значений (раздел 3). Если значения нет в шкале — его нельзя использовать.
1.4. Контраст и визуальная иерархия
Пользователь не читает страницу — он её сканирует. Иерархия отвечает на вопрос «куда смотреть первым». Строится четырьмя средствами, по силе воздействия:
- Размер — самый сильный сигнал. Разница между уровнями должна быть заметной: не 20 и 22 px, а 20 и 32.
- Вес и цвет — второстепенный текст делается серым (но с соблюдением контраста, см. 5.2), а не мелким.
- Пространство — важный блок окружён воздухом.
- Позиция — верх и левый край читаются первыми (для языков с письмом слева направо).
На экране должен быть ровно один элемент первого уровня. Если у вас две одинаково крупные кнопки «Купить» и «Узнать больше» — иерархии нет, и конверсия падает у обеих. Вторая кнопка должна быть визуально слабее (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 колонки |
Базовые стили пишутся без медиа-запросов и описывают мобильную версию. Всё, что добавляется на больших экранах, идёт в @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→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. Иначе карточки «слипаются» в единое поле.
✕ padding 24, gap 8
✓ padding 24, gap 32
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% ширины окна. Один раз написали — работает на всех экранах.
Если средний аргумент задан только в 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× размера шрифта |
| отступ после абзаца | не менее 2× размера шрифта |
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;
} Подключение шрифтов с fonts.googleapis.com — это внешний запрос, дополнительное DNS-разрешение и, в ряде юрисдикций (в частности, по решениям судов в ЕС по GDPR), передача IP-адреса пользователя третьей стороне. Скачивайте шрифты и кладите в тему. В блочных темах это делается через theme.json → settings.typography.fontFamilies.fontFace (см. раздел 9).
4.8. Микротипографика
- Неразрывный пробел
— между числом и единицей (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. Структура палитры
Цвета описываются в два слоя. Сначала «сырые» оттенки, потом семантические токены, ссылающиеся на них. Это позволяет переключить тему в одном месте.
Правило: 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. Порядок тестирования
- 320 px — минимальная ширина по нормативу (iPhone SE 1-го поколения).
- 375 / 390 px — реальный массовый телефон.
- 768 px — планшет вертикально.
- 1024 px — планшет горизонтально / небольшой ноутбук.
- 1440 px — типовой десктоп.
- 1920 px — проверка, что контейнер не расползся.
- Зум 200% и 400% на 1280 px — проверка SC 1.4.4 и 1.4.10.
9. Реализация: theme.json и Gutenberg
Вся система из разделов 3–6 описывается в одном файле theme.json в корне темы. Это не «ещё один способ написать CSS» — это конфигурация, из которой WordPress:
- генерирует CSS-переменные вида
--wp--preset--spacing--50; - строит интерфейс редактора — селекторы отступов, шрифтов и цветов показывают именно ваши значения;
- убирает из интерфейса всё, что вы не разрешили.
Дизайн-система работает не тогда, когда она описана, а когда контент-менеджер физически не может поставить отступ 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. Дочерняя тема и порядок переопределения
Приоритет, от низшего к высшему:
- Дефолты ядра WordPress (
wp-includes/theme.json); theme.jsonродительской темы;theme.jsonдочерней темы;- Global Styles — то, что редактор сохранил через интерфейс (хранится в БД, в записи типа
wp_global_styles); - Стили конкретного блока, заданные в редакторе (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; } 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 (перевод и адаптация — авторские):
- Web Content Accessibility Guidelines (WCAG) 2.2 — основной норматив
- Understanding WCAG 2.2 — пояснительные документы к каждому критерию
- SC 1.4.12 Text Spacing — интервалы текста
- SC 2.5.8 Target Size (Minimum) — размеры целей нажатия
- SC 1.4.10 Reflow — 320 CSS px
- SC 1.4.3 Contrast (Minimum) — контраст текста
- SC 1.4.11 Non-text Contrast — контраст интерфейсных элементов
- SC 1.4.8 Visual Presentation — длина строки, интерлиньяж, выключка
- WAI Web Accessibility Tutorials — практические руководства по страницам, формам, изображениям, таблицам
- Accessibility Requirements for People with Low Vision — обоснование требований к интервалам и размерам
- CSS Values and Units Module Level 3 — определение CSS-пикселя и единиц
ch,rem,vw - CSS Grid Layout Module Level 1, CSS Flexible Box Layout Module Level 1 — сетки и
gap - CSS Box Model Module Level 3 — схлопывание отступов
- ARIA Authoring Practices Guide — эталонное поведение компонентов (модалки, аккордеоны, табы, меню)
Документация WordPress по theme.json: Theme.json Reference и Global Settings & Styles.
Значения без пометки «норматив» — устоявшаяся практика, а не требование стандарта. Они рабочие, но не единственно возможные: важнее, чтобы система была последовательной, чем чтобы она совпадала с этой таблицей.
Нормативные требования актуальны на дату публикации WCAG 2.2. Перед сдачей проекта с юридическими обязательствами по доступности сверяйтесь с первоисточником.
