Почти любое значение размера в 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, высота строки корневого элемента: он не меняется от блока к блоку и годится для общей вертикальной сетки страницы.
Проценты: относительно чего на самом деле
Процент выглядит самой понятной единицей: например, 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-единицы.
Единицы от контейнера: 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 ищется глазами.
Когда единица не одна, а диапазон
Часто правильный ответ звучит не «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 возвращает тексту способность расти вместе с пользовательской настройкой.
Какую единицу брать под задачу
Сводка, к которой удобно возвращаться. Вопрос всегда один: от чего этот размер обязан зависеть.
| Задача | Единица | Почему |
| Размер текста | 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 много, но выбор каждый раз сводится к одному вопросу: от чего этот размер обязан зависеть — от шрифта, от родителя, от экрана или ни от чего. Ответили на него — единица выбирается сама, а макет перестаёт ломаться от чужих настроек и нестандартных экранов. Ближайшая соседняя тема — разница между адаптивной вёрсткой и респонсивом: там те же единицы, но с точки зрения общей стратегии макета.
Комментарии (0)