PX vs REM vs EM: полное руководство по единицам CSS
Короткий ответ на вопрос px vs rem vs em, ещё до всяких объяснений. Используйте rem почти для всех размеров: размеров шрифта, отступов, полей, промежутков, скруглений и контрольных точек. Rem масштабируется вместе с настройкой размера шрифта в браузере читателя. Используйте px для тех немногих вещей, которые не должны масштабироваться никогда, — например, для границы в 1px или точного смещения тени. Используйте em для редкого локального случая, когда значение должно расти вместе с собственным размером шрифта элемента: скажем, для внутренних отступов кнопки, привязанных к её тексту.
Это правило покрывает 90% решений. На остальные 10% и приходится большинство проблем: математика вложенных em, которая удивляет каждого при первой встрече; баг медиазапроса, ломающий вёрстку при масштабировании; случаи, где px на самом деле оказывается более доступным выбором. Дальше я разбираю все три темы с готовым CSS для каждой и даю шпаргалку по свойствам, которую можно держать открытой, пока вы пишете стили.
Что на самом деле означают px, rem и em
У трёх единиц три разные точки отсчёта, и в этом всё различие.
px — это абсолютная единица. Один px — это один пиксель CSS, и он остаётся такого же размера независимо от окружения. border: 1px solid — это один пиксель, и точка. Подвох в том, что «абсолютная» означает ещё и «игнорирующая» предпочтения пользователя — почему это важно, поговорим ниже.
rem отсчитывается от размера шрифта корневого элемента. Корень — это <html>, и браузеры по умолчанию задают ему размер шрифта 16px. Поэтому 1rem равен 16px при стандартных настройках, везде на странице и независимо от вложенности. Ради этой предсказуемости rem и выбирают: одно опорное значение и никаких сюрпризов.
em отсчитывается от размера шрифта текущего элемента (или его родителя — для свойств, отличных от font-size). Поскольку эта точка отсчёта меняется по мере вложения элементов, значения em сдвигаются в зависимости от контекста. Один и тот же 1.5em может превратиться в 24px в одном месте и в 30px — в другом.
Запомнить стоит одно: 16px = 1rem. Если усвоите из всего только это, уже хватит. Когда нужно перевести конкретное значение, конвертер px в rem выполнит деление на любую выбранную вами базу.
px vs rem vs em — кратко
| Единица | Относительно чего | Масштабируется с размером шрифта пользователя? | Типичное применение | Поведение при вложенности |
|---|---|---|---|---|
px | Ничего (абсолютная) | Нет | Границы, смещения теней, тонкие линии | Всегда одного размера |
rem | Размер шрифта корня <html> | Да | Размер шрифта, отступы, контрольные точки | Всегда одного размера |
em | Размер шрифта текущего элемента | Да | Локальные значения, привязанные к компоненту | Накапливается — может «уплывать» |
Главные тут два столбца: «масштабируется с размером шрифта пользователя» и «поведение при вложенности». rem выигрывает по обоим: он уважает предпочтение читателя и остаётся предсказуемым. em разделяет первое преимущество, но жертвует вторым.
Как вычисляется каждая единица
Математика тут — обычная арифметика. Людей сбивает с толку то, на какое число делить или умножать.
rem использует размер шрифта корня:
rem = px ÷ root-font-size
При корне в 16px по умолчанию 24px ÷ 16 = 1.5rem. Чтобы вернуться обратно, умножаем: 1.5rem × 16 = 24px. Каждый rem на странице использует одно и то же значение 16 (или то, что вы задали корню), — именно поэтому rem предсказуем.
em использует собственный размер шрифта текущего элемента:
em = px ÷ current-element-font-size
Если у элемента font-size равен 20px, то 1em для этого элемента — это 20px, 0.5em — это 10px, а отступ 1.5em — это 30px. Измените размер шрифта элемента, и каждое привязанное к нему значение em изменится вместе с ним. В этой локальной связанности и смысл em, и его ловушка.
Ловушка накопления em
Именно здесь em чаще всего и подводит. Когда вы вкладываете элементы, которые все используют em для размера шрифта, значения перемножаются вниз по дереву. Каждый уровень наследует вычисленный размер шрифта своего родителя и применяет поверх собственный коэффициент em.
.menu { font-size: 1.2em; } /* родитель 16px → 19.2px */
.menu .item { font-size: 1.2em; } /* родитель 19.2px → 23.04px */
.menu .item .sub { font-size: 1.2em; } /* родитель 23.04px → 27.648px */
Каждый уровень — это «120% от своего родителя», что звучит безобидно. Но поскольку родитель уже вырос, третий уровень составляет 1.2 × 1.2 × 1.2 = 1.728em относительно исходных 16px — около 27.6px, а не 19.2px, которые вы могли бы вывести из правила, рассматривая его изолированно. Вложите список внутрь списка внутри компонента — и текст раздуется так, что отследить причину будет непросто.
rem обходит это полностью. 1.2rem — это 19.2px, стоит ли он в начале документа или на двенадцатом уровне вложенности, потому что rem всегда отмеряется от корня, а не от родителя. Когда значение разрешается в неожиданный размер, первый вопрос — это em (относительно родителя, накапливается) или rem (относительно корня, стабильно)? Если вы отлаживаете «заблудившийся» rem и хотите быстро увидеть его размер в пикселях, конвертер rem в px разрешит его мгновенно.
Когда использовать rem
По умолчанию тянитесь за rem. Это правильная единица для размеров шрифта, отступов, полей, промежутков, скруглений и контрольных точек медиазапросов — для всего, что должно масштабироваться, когда читатель меняет размер текста.
Последняя оговорка — это и есть аргумент о доступности, и он не гипотетический. Опросы WebAIM среди пользователей скринридеров и людей со слабым зрением стабильно показывают, что значительная доля пользователей меняет размер шрифта по умолчанию в браузере или ОС, причём многие — заметно выше стандартных 16px. Вёрстка с размерами в rem уважает это изменение: поднимите значение по умолчанию до 20px, и каждое значение на основе rem вырастет пропорционально. Вёрстка с размерами в px игнорирует его полностью — текст остаётся прикованным к жёстко заданному размеру, как бы сильно читателю ни требовался размер крупнее.
:root {
font-size: 16px; /* 1rem = 16px */
}
h1 { font-size: 2rem; } /* 32px, масштабируется с настройкой пользователя */
p { font-size: 1rem; } /* 16px */
.card { padding: 1.5rem; } /* 24px */
.card { border-radius: 0.5rem; } /* 8px */
Поскольку каждое значение здесь привязано к одному и тому же корню, единственное изменение размера корневого шрифта пропорционально пересчитывает весь интерфейс. Так и держится цельность дизайн-системы: отступы и типографика двигаются вместе, а не расходятся врозь.
Трюк с 62.5%
Есть популярный приём, чтобы сделать арифметику rem тривиальной. Задайте размер корневого шрифта равным 62.5%, то есть 62.5% × 16px = 10px:
html {
font-size: 62.5%; /* теперь 1rem = 10px */
}
body {
font-size: 1.6rem; /* восстанавливаем читаемые 16px текста */
}
h1 { font-size: 2.4rem; } /* 24px */
p { font-size: 1.6rem; } /* 16px */
С корнем в 10px устный счёт сводится к «раздели значение в пикселях на 10»: 24px → 2.4rem, 12px → 1.2rem. Единственная тонкость — восстановить читаемый размер текста через body { font-size: 1.6rem }, поскольку при сырой базе 10px текст по умолчанию слишком мелкий. Использование 62.5% как процента, а не 10px, сохраняет относительность — так что читатель, который масштабирует значение браузера по умолчанию, всё равно получает пропорциональный рост. Если вы берёте эту базу, задайте размер корневого шрифта в конвертере равным 10, чтобы он совпадал с вашей таблицей стилей.
Когда использовать em
Используйте em, когда хотите, чтобы значение масштабировалось вместе с собственным размером шрифта элемента, а не корня. Классический случай — кнопка:
.btn {
font-size: 1rem; /* размер относительно корня */
padding: 0.75em 1.5em; /* отступы привязаны к тексту кнопки */
}
.btn--large {
font-size: 1.25rem; /* одно изменение меняет размер всего */
}
Поскольку отступы заданы в em, модификатор .btn--large меняет размер текста и его отступов вместе из одного объявления — кнопка остаётся пропорциональной при любом размере. Та же логика применима к иконке с размером в em, чтобы она совпадала со строкой текста, рядом с которой стоит, или к межбуквенному интервалу, который должен расти вместе со шрифтом.
На практике работает так: rem для глобального каркаса, em для локальных пропорций. Задавайте размер шрифта в rem, чтобы он отвечал корню и предпочтению пользователя; ту горстку значений, которые должны следовать за этим элементом, задавайте в em. Только держите em подальше от всего, что глубоко вкладывается, иначе ловушка накопления из предыдущего раздела вернётся.
Когда использовать px
Некоторые значения действительно не должны масштабироваться, и px для них правилен: тонкая граница в 1px, точное смещение box-shadow, фокусное кольцо в 2px. Это детали отрисовки, а не контент. Граница, «масштабирующаяся» до 1.25px, когда пользователь увеличивает текст, ничего не выигрывает и может отрисоваться как размытая линия. px сохраняет её чёткой.
.divider { border-bottom: 1px solid; } /* должна остаться 1px */
.card { box-shadow: 0 2px 4px rgba(0,0,0,0.1); } /* фиксированное смещение */
.input:focus { outline: 2px solid; } /* чёткое фокусное кольцо */
Неожиданный нюанс — когда px доступнее
А вот часть, которую советы «всегда используйте rem» обычно опускают. Доступный выбор — это не «rem везде», а «масштабируй то, что должно масштабироваться, и фиксируй то, что не должно».
Граница в 1px — это фиксированная деталь. Если втиснуть её в rem, чтобы она росла, когда пользователь увеличивает текст, читаемости это не добавит, а тонкую линию сделает размытой. Для таких свойств px — более доступный выбор именно потому, что остаётся на месте.
Реальная ошибка, как правило, обратная: использование px для того, что должно реагировать, например для размеров шрифта и контрольных точек. Вот где px вредит доступности. Так что правило не про единицу, а про свойство. Спросите себя: это значение — контент, с которым взаимодействует читатель (масштабируй — rem), или фиксированная деталь отрисовки (фиксируй — px)? Единица вытекает из ответа.
Ловушка медиазапроса
Эта ловушка ломает реальную вёрстку, а предупреждают о ней редко. Контрольные точки медиазапросов, написанные в px, не реагируют на масштабирование размера шрифта в браузере так, как вам бы хотелось.
Представьте контрольную точку width: 600px, на которой сворачивается боковая панель. Пользователь со слабым зрением задаёт размер браузера по умолчанию в 24px, чтобы читать комфортно. Теперь вашему контенту нужно больше горизонтального места — более крупный текст хочет перетечь раньше. Но контрольная точка в px не знает, что текст вырос; она всё так же срабатывает ровно на 600px ширины окна, поэтому вёрстка переключается не в тот момент, и контент сжимается или накладывается.
Сравните два подхода:
/* контрольная точка в px — игнорирует настройку размера шрифта пользователя */
@media (min-width: 600px) {
.sidebar { display: block; }
}
/* контрольная точка в em/rem — реагирует на размер шрифта пользователя */
@media (min-width: 37.5em) {
.sidebar { display: block; }
}
37.5em — это 600px при стандартных 16px (600 ÷ 16 = 37.5). Различие в поведении: когда пользователь удваивает размер шрифта по умолчанию, контрольная точка в em фактически тоже удваивается, так что вёрстка переключается при ширине окна, пропорциональной тексту, — ровно тогда, когда это нужно контенту. Контрольная точка в px остаётся замороженной.
Одна тонкость, о которой стоит знать: в условиях медиазапросов em и rem оба отсчитываются от размера шрифта браузера по умолчанию, а не от какого-либо переопределения в html, поэтому там они ведут себя одинаково. Любая из единиц исправляет баг; px его вызывает.
Таблица решений по свойствам
Когда вы сомневаетесь, эта таблица даёт ответ, не заставляя выводить логику заново каждый раз.
| Свойство | Рекомендуемая единица | Почему |
|---|---|---|
font-size | rem | Масштабируется с настройкой размера шрифта пользователя |
padding / margin | rem | Отступы масштабируются вместе с текстом |
border | px | Тонкие линии должны оставаться чёткими и фиксированными |
смещение box-shadow | px | Точная деталь отрисовки, а не контент |
border-radius | rem | Сохраняет скругление углов пропорциональным масштабу |
| медиазапрос | em / rem | Контрольные точки должны реагировать на масштаб шрифта |
width / max-width | rem (часто ch для текста) | Масштабируемая ширина вёрстки; ch ограничивает длину строки |
line-height | без единицы | Безразмерный множитель наследуется правильно |
Строка line-height заслуживает примечания, потому что это частый баг. Всегда пишите line-height: 1.5, без единицы. Безразмерное значение — это множитель, который каждый элемент вычисляет относительно собственного размера шрифта, поэтому вложенные элементы остаются читаемыми. Напишите line-height: 1.5em или 24px — и наследуется вычисленная длина, а значит дочерний элемент с более крупным шрифтом сохранит межстрочный интервал родителя, и его текст начнёт налезать. Безразмерное значение снимает всю проблему.
Перевод между px и rem
Арифметика достаточно мала, чтобы делать её в уме, как только вы держите якорь: 16px = 1rem. Делите на 16, чтобы перейти к rem, умножайте на 16, чтобы вернуться к px.
| px | rem (база 16px) |
|---|---|
| 8px | 0.5rem |
| 12px | 0.75rem |
| 16px | 1rem |
| 24px | 1.5rem |
| 32px | 2rem |
Если вы используете трюк с 62.5%, база становится 10px, и математика ещё проще — просто делите или умножайте на 10, так что 24px = 2.4rem. Единственное правило — всегда переводите относительно той базы, которую ваша таблица стилей действительно задаёт.
Для всего остального — нестандартных значений, кастомного корня или пакетного перевода экспорта из Figma — пропустите устный счёт и используйте конвертер px в rem или конвертер rem в px. Оба позволяют задать любой размер корневого шрифта и переводить в любом направлении в реальном времени. А если потом вы приводите в порядок таблицу стилей с перемешанными единицами, форматтер CSS нормализует за вас пробелы и отступы.
Частые ошибки
Большую часть проблем с единицами вызывают несколько повторяющихся шаблонов:
Задание размера корневого шрифта в px. Запись html { font-size: 16px } (вместо того чтобы оставить значение по умолчанию или использовать 100% / процент) напрямую переопределяет настройку размера шрифта в браузере пользователя. Значения rem по-прежнему вычисляются относительно него, но читатель больше не может масштабировать всю страницу. Оставьте корень по умолчанию или используйте процент.
Смешивание px и rem без системы. Одни размеры шрифта в px, другие в rem, отступы поделены между обоими — в результате вёрстка масштабируется неравномерно, когда пользователь меняет размер текста. Выберите rem по умолчанию и приберегите px для осознанных исключений из таблицы решений.
Использование em для глобальных отступов. Em на широко вложенных контейнерах вновь вводит ловушку накопления, так что padding глубоко в дереве разрешается во что-то, чего никто не задумывал. Держите глобальные отступы в rem; приберегите em для локальных значений в рамках компонента.
Указание единицы у line-height. Как описано выше, line-height: 24px или 1.5em наследуется как вычисленная длина и ломается на элементах с другим размером шрифта. Всегда используйте безразмерный множитель.
FAQ
rem лучше, чем px?
Для большинства размеров — да: rem лучше px, потому что масштабируется с настройкой размера шрифта в браузере пользователя, которую px игнорирует. Но «лучше» зависит от свойства: px — правильный выбор для фиксированных деталей вроде границ в 1px и смещений теней, которые должны оставаться чёткими. Используйте rem для размеров контента, px — для деталей отрисовки.
Чему равен 1rem в пикселях?
1rem равен размеру корневого шрифта в пикселях, который по умолчанию составляет 16px практически во всех браузерах. Значит, 1rem = 16px, 1.5rem = 24px, а 2rem = 32px при стандартных настройках. Если таблица стилей переопределяет html { font-size } — например, до 10px через трюк с 62.5%, — то 1rem равен уже этому значению.
Использовать rem или em для font-size?
Используйте rem для font-size почти в каждом случае. Rem отмеряется от корня, поэтому остаётся предсказуемым независимо от глубины вложенности элемента. Em отмеряется от размера шрифта родителя, что накапливается вниз по дереву и заставляет вложенный текст раздуваться неожиданно. Приберегите em для локальных значений, привязанных к одному компоненту.
Когда использовать px вместо rem?
Используйте px для значений, которые не должны масштабироваться с размером шрифта пользователя: границ в 1px, точных смещений box-shadow, контуров фокусных колец и других фиксированных деталей отрисовки. Это чёткие детали оформления, а не контент, поэтому фиксировать их в px — более доступный выбор. Всё, что относится к контенту, всё равно должно использовать rem.
Почему медиазапросы ломаются, когда я использую px?
Контрольные точки медиазапросов в px не реагируют на масштабирование размера шрифта в браузере. Когда пользователь увеличивает шрифт по умолчанию, его контенту нужно больше места, но контрольная точка в px всё так же срабатывает на той же ширине окна — поэтому вёрстка переключается не в тот момент. Используйте контрольные точки в em или rem, которые масштабируются с размером шрифта пользователя.
Что такое трюк с font-size 62.5%?
Трюк с 62.5% задаёт html { font-size: 62.5% }, делая размер корневого шрифта равным 10px (62.5% от 16). С базой в 10px математика rem становится «делением на 10»: 24px = 2.4rem, 12px = 1.2rem. Затем разработчики задают body { font-size: 1.6rem }, чтобы восстановить читаемые 16px текста.
Можно ли смешивать px, rem и em?
Да, смешивать px, rem и em правильно, когда каждая единица следует подходящему ей свойству: rem для типографики и отступов, px для фиксированных деталей, em для локальных значений в рамках компонента. Проблемы вызывает смешивание без системы — например, когда одни размеры шрифта в px, а другие в rem. Выберите rem по умолчанию, а px и em трактуйте как осознанные исключения.
Какую единицу использовать для padding и margin?
Используйте rem для padding и margin, чтобы отступы масштабировались вместе с текстом, когда пользователь меняет размер шрифта. Это сохраняет вёрстку пропорциональной и доступной. Приберегите em для отступов, которые должны следовать за собственным размером шрифта элемента, — например, кнопки, у которой отступы растут вместе с её текстом, — и избегайте em на глубоко вложенных контейнерах, где он накапливается.