Почти любое значение размера в CSS — это пара «число + единица». Число подобрать легко, а вот единица решает куда больше, чем кажется: она отвечает не на вопрос «сколько», а на вопрос «относительно чего». Запись 16px всегда означает одно и то же, а 1em у соседнего элемента может оказаться и 14 пикселями, и 24 — в зависимости от того, где этот элемент лежит.

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

Абсолютные единицы: px и печатная родня

Абсолютные единицы ни от чего не зависят: сколько написали, столько и получилось. В вебе из всей группы реально используется одна — px.

.divider {
  border-bottom: 1px solid #d0d0d0;
  border-radius: 4px;
}

Главное недоразумение вокруг px — будто это физический пиксель экрана. Нет: спецификация определяет CSS-пиксель как 1/96 дюйма (это называют эталонным пикселем). На экране с высокой плотностью точек один CSS-пиксель рисуется несколькими физическими, поэтому линия в 1px выглядит одинаково тонкой и на старом мониторе, и на ноутбуке с retina-экраном.

Остальные абсолютные единицы завязаны на настоящие дюймы и сантиметры и в экранной вёрстке бесполезны — браузер всё равно пересчитает их в те же CSS-пиксели по фиксированному курсу.

Единица Сколько это Где встречается
px 1/96 дюйма вся экранная вёрстка
pt 1/72 дюйма типографика в стилях для печати
in, cm, mm, Q дюйм, сантиметр, миллиметр, четверть миллиметра поля и размеры страницы при печати
pc 12 пунктов (1/6 дюйма) наследие полиграфии, в вебе не используется

Единственное место, где печатные единицы уместны, — блок стилей для печати. Там у страницы действительно есть физический размер, и pt с cm перестают быть абстракцией:

@media print {
  @page { margin: 2cm; }

  body {
    font-size: 11pt;
    line-height: 1.4;
  }
}

И один нюанс, из-за которого px не стоит ставить в размер текста. Масштабирование страницы (Ctrl + колесо) увеличивает всё, включая пиксели, — тут проблем нет. Но в настройках браузера есть отдельный пункт «размер шрифта по умолчанию», которым люди со слабым зрением поднимают базовый кегль до 20–24 пикселей. Этот пункт меняет размер шрифта корневого элемента — и текст, жёстко заданный в px, его просто игнорирует.

Единицы от шрифта: em, rem, ch, lh

Эта группа отсчитывается от типографики — от размера шрифта, от формы конкретных букв или от высоты строки.

em — от собственного размера шрифта

Единица em равна вычисленному font-size того элемента, на котором её написали. Если у кнопки шрифт 18 пикселей, то padding: 0.75em 1.5em даст 13,5 и 27 пикселей — и пересчитается сам, если кегль поменяется.

.action-button {
  font-size: 18px;
  padding: 0.75em 1.5em;   /* 13.5px и 27px */
  border-radius: 0.5em;    /* 9px */
}

Это и есть сильная сторона em: элемент масштабируется целиком, достаточно поменять одну строку. Но у свойства font-size особый случай — там отсчёт идёт не от себя (себя ещё не существует), а от размера шрифта родителя. Отсюда эффект снежного кома во вложенных структурах:

.tree li {
  font-size: 0.9em;
}

/* первый уровень  — 0.9 от базового
   второй уровень  — 0.81
   третий уровень  — 0.729 … и текст уползает в нечитаемый */

rem — от корня документа

Единица rem (root em) считается от размера шрифта корневого элемента <html> и не зависит от глубины вложенности. По умолчанию это 16 пикселей, значит 1rem = 16px, 1.5rem = 24px.

.card-title { font-size: 1.5rem; }
.card-text  { font-size: 1rem; }
.card       { padding: 1.5rem; gap: 0.75rem; }

Главный аргумент за rem — доступность. Когда посетитель поднял размер шрифта по умолчанию в настройках браузера, корневой кегль вырастает, а вместе с ним и весь макет на rem. Ничего не ломается и ничего не остаётся мелким.

Среди приёмов часто встречается уменьшение корневого шрифта, чтобы считать в уме: html { font-size: 62.5% } даёт 1rem = 10px, и дальше 2.4rem читается как 24 пикселя. Приём рабочий: проценты считаются от пользовательской настройки, поэтому масштабирование сохраняется. А вот вариант html { font-size: 10px } выглядит так же, но прибивает корень к абсолютному значению — и отменяет ровно ту пользу, ради которой rem и берут.

ch, ex, cap — от формы букв

Три единицы отсчитываются не от кегля, а от того, как выглядят конкретные глифы текущего шрифта: ch — ширина символа «0», ex — высота строчной буквы «x», cap — высота заглавной. Сменился font-family — поменялись и они.

На практике из этой тройки постоянно нужна одна: ch отлично задаёт длину строки. Типографская рекомендация — 60–75 символов в строке, и в ch это пишется буквально:

.article-body {
  max-width: 68ch;
  margin-inline: auto;
}

Только держать в голове: ch — это ширина нуля, а не средняя ширина символа. В пропорциональном шрифте «i» уже нуля, а «m» шире, так что реальных символов в строке окажется больше заявленного числа. Для текста это не беда: ориентир всё равно приблизительный.

lh — высота строки как единица

Единица lh равна вычисленному line-height элемента. Она закрывает задачу, которую раньше решали калькулятором: сделать вертикальные отступы кратными строке, чтобы текст в соседних колонках попадал в одну сетку.

.post p {
  line-height: 1.6;
  margin-block-end: 1lh;   /* ровно одна пустая строка */
}

.post blockquote {
  padding-block: 0.5lh;
  border-inline-start: 0.25lh solid #859900;
}

Есть и корневой вариант — rlh, высота строки корневого элемента: он не меняется от блока к блоку и годится для общей вертикальной сетки страницы.

Поддержка браузерами
chrome
Chrome
109
firefox
Firefox
120
edge
Edge
109
safari
Safari
16.4
opera
Opera
95

Проценты: относительно чего на самом деле

Процент выглядит самой понятной единицей: например, 50% - это половина от родителя и всё. И действительно с шириной это так и работает.

.gallery {          /* содержащий блок шириной 600px */
  width: 600px;
}

.gallery__item {
  width: 50%;       /* 300px — от ширины .gallery */
}

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

Вот, какие могут быть расхождения:

Свойство Процент считается от
width, left, right ширины содержащего блока
height, top, bottom высоты содержащего блока — но только если она задана явно
padding и margin по всем четырём сторонам ширины содержащего блока, даже для верха и низа
font-size размера шрифта родителя (то же, что em)
line-height собственного размера шрифта элемента
translate и translateX/Y размеров самого элемента

Две строки из таблицы стоят отдельного слова.

Первая — вертикальные отступы в процентах. То, что padding-top: 50% считается от ширины, выглядит как опечатка в спецификации, но именно на этом много лет держался единственный способ зарезервировать место под картинку в нужной пропорции. Сейчас для этого есть свойство aspect-ratio, а старый приём остаётся в чужом коде, который приходится читать.

Вторая — line-height в процентах. Проценты вычисляются в конкретное число пикселей прямо на том элементе, где написаны, и дальше наследуются уже этим числом. Задали line-height: 150% на body с кеглем 16px — все потомки получат жёсткие 24px, включая заголовок с кеглем 40px, где строки налезут друг на друга. Безразмерное значение наследуется как множитель и пересчитывается у каждого элемента от его собственного шрифта:

body {
  font-size: 16px;
  line-height: 1.5;   /* не 150% — тогда заголовки посчитают его от своего кегля */
}

Единицы от вьюпорта: vw, vh и что с ними не так

Вьюпорт — это видимая область страницы в окне браузера. Проценты от неё записываются четырьмя классическими единицами:

  • vw — 1% ширины вьюпорта;
  • vh — 1% высоты;
  • vmin — 1% меньшей из двух сторон;
  • vmax — 1% большей.

Пара vmin / vmax удобна там, где элемент обязан помещаться в экран в любой ориентации — например, квадратная карточка или полноэкранная иллюстрация:

.badge {
  width: 40vmin;    /* в портретной ориентации считается от ширины,
                       в альбомной — от высоты, но всегда влезает */
  aspect-ratio: 1;
}

Есть и логические близнецы — vi и vb: процент от вьюпорта вдоль строчной и блочной оси. В привычной горизонтальной раскладке они совпадают с vw и vh, а в вертикальном письме меняются местами.

Две ловушки, на которые наступают все:

Ширина 100vw шире страницы. На десктопе с классической полосой прокрутки 100vw включает её ширину, а доступное содержимому место — нет. Результат: блок на всю ширину экрана вылезает на 15–17 пикселей и появляется горизонтальный скролл. Для элемента, растянутого по ширине родителя, надёжнее width: 100%.

Высота 100vh не равна видимой части экрана на телефоне. Мобильные браузеры прячут адресную строку при скролле, и старый vh зафиксирован на большем из состояний — низ блока уезжает под панель. Для этого и появилось семейство svh, lvh, dvh, разобранное отдельно в материале про новые viewport-единицы.

Поддержка браузерами
chrome
Chrome
108
firefox
Firefox
101
edge
Edge
108
safari
Safari
15.4
opera
Opera
94

Единицы от контейнера: cqi, cqw и родня

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

  • cqw и cqh — 1% ширины и высоты контейнера;
  • cqi и cqb — то же самое вдоль строчной и блочной оси;
  • cqmin и cqmax — 1% меньшей и большей из этих двух сторон.

Чтобы они заработали, предку нужно объявить себя контейнером через container-type:

.card-slot {
  container-type: inline-size;
}

.card__title {
  /* не меньше 1rem, не больше 1.75rem, между ними — 6% ширины слота */
  font-size: clamp(1rem, 6cqi, 1.75rem);
}

.card {
  padding: 4cqi;
  gap: 2cqi;
}

Одна и та же карточка в широкой колонке получает крупный заголовок и щедрые отступы, в узком сайдбаре — компактные, и всё это без единого медиа-запроса. Подробный разбор самого механизма контейнеров — в руководстве по Container Queries.

Отдельно стоит знать про запасное поведение: если подходящего контейнера у элемента не нашлось, контейнерные единицы считаются от малого вьюпорта — то есть cqi молча превращается в svi. Ошибки в консоли не будет, вёрстка просто окажется не того размера, поэтому забытый container-type ищется глазами.

Поддержка браузерами
chrome
Chrome
105
firefox
Firefox
110
edge
Edge
105
safari
Safari
16.0
opera
Opera
91

Когда единица не одна, а диапазон

Часто правильный ответ звучит не «rem или vw», а «и то, и другое, но в границах». Эту задачу закрывают функции min(), max() и clamp(), которые умеют смешивать единицы прямо в одном значении.

.hero__title {
  /* минимум, гибкая середина, максимум */
  font-size: clamp(1.75rem, 1.25rem + 2.5vw, 3.5rem);
}

.layout {
  width: min(100%, 72rem);        /* не шире 72rem и не шире родителя */
  padding-inline: max(1rem, 4vw); /* на узких экранах — фиксированный минимум */
}

В среднем аргументе clamp() стоит держать слагаемое в rem, а не одни только vw. Причина не в красоте: размер, целиком завязанный на ширину окна, не реагирует на изменение размера шрифта в настройках браузера и мешает увеличить текст. Критерий доступности WCAG (Web Content Accessibility Guidelines — международный свод требований к доступности сайтов) требует, чтобы текст можно было увеличить вдвое без потери содержимого, и запись вида font-size: 4vw его нарушает. Слагаемое в rem возвращает тексту способность расти вместе с пользовательской настройкой.

Поддержка браузерами
chrome
Chrome
79
firefox
Firefox
75
edge
Edge
79
safari
Safari
13.1
opera
Opera
66

Какую единицу брать под задачу

Сводка, к которой удобно возвращаться. Вопрос всегда один: от чего этот размер обязан зависеть.

Задача Единица Почему
Размер текста rem уважает настройку размера шрифта в браузере
Отступы и скругления внутри компонента em масштабируются вместе с кеглем самого компонента
Общие отступы макета, сетка rem единая шкала, не зависящая от вложенности
Рамки, тени, тонкие линии px линия в полпикселя не нужна никому
Ширина колонки текста ch длина строки измеряется в символах, а не в пикселях
Вертикальный ритм абзацев lh отступ кратен строке автоматически
Ширина блока внутри родителя % прямая доля содержащего блока
Полноэкранная секция svh / dvh учитывают панели мобильного браузера
Элемент, влезающий в экран в любой ориентации vmin считается от меньшей стороны
Компонент, который кладут в разные по ширине места cqi зависит от контейнера, а не от экрана
Размер, который должен плавать между границами clamp() смешивает фиксированную и гибкую часть
Печатная версия страницы pt, cm у листа бумаги действительно есть сантиметры

Частые ошибки

Размер шрифта в пикселях по всему проекту. Работает, выглядит предсказуемо и ровно до тех пор, пока к сайту не придёт человек, поднявший размер шрифта в настройках браузера. Для него ничего не изменится. Лечится заменой на rem в размерах текста; рамки и тени в px оставлять можно.

Цепочка вложенных em. Если font-size в em стоит на элементе, который вкладывается сам в себя — список, дерево комментариев, меню, — размеры перемножаются на каждом уровне. Для шрифта в таких структурах берётся rem, а em остаётся на отступах.

height: 100% без высоты у родителя. Процент нужно от чего-то считать: если у родителя высота auto, значение просто игнорируется. Обычно задачу лучше решает min-height в dvh или растягивание через grid/flex.

Проценты в line-height на body. Значение вычислится один раз и разъедется по всем потомкам жёстким числом пикселей. Безразмерный множитель делает то, чего от процентов и ждали.

Ширина в vw у блока внутри контейнера. Классический источник горизонтального скролла: 100vw не знает ни про полосу прокрутки, ни про отступы родителя. Внутри потока почти всегда нужен %, а размеры по содержимому закрывают ключевые слова min-content, max-content и fit-content.

Единиц в CSS много, но выбор каждый раз сводится к одному вопросу: от чего этот размер обязан зависеть — от шрифта, от родителя, от экрана или ни от чего. Ответили на него — единица выбирается сама, а макет перестаёт ломаться от чужих настроек и нестандартных экранов. Ближайшая соседняя тема — разница между адаптивной вёрсткой и респонсивом: там те же единицы, но с точки зрения общей стратегии макета.