Адаптивная типографика на clamp() без JavaScript
Когда я в первый раз увидела clamp() в продовом коде, у меня была ровно одна мысль: «зачем это месиво из трёх чисел, если есть простые media queries». Через полгода работы я перешла на clamp на 90% типографики и больше не возвращалась. Сейчас расскажу, почему.
Эта статья — про адаптивную типографику без JavaScript, без подключения сторонних библиотек, без сотни breakpoints в CSS. Только нативный clamp(), немного математики и одна идея, которая меняет подход к шрифтам в проекте.
Что не так с media queries для шрифтов
Классический подход:
h1 {
font-size: 24px;
}
@media (min-width: 768px) {
h1 {
font-size: 32px;
}
}
@media (min-width: 1280px) {
h1 {
font-size: 48px;
}
}Что тут плохо. Между breakpoints шрифт скачет. На 767px он 24px, на 768px резко становится 32px. Между 768 и 1280 — фиксированный, хотя ширина экрана меняется на 500+ пикселей. На больших мониторах текст всё равно выглядит мелким относительно ширины.
Альтернатива «добавить ещё breakpoints» — это путь в ад. Дизайн-система с десятью breakpoints для типографики не проектируется и не поддерживается. На каждый шаг приходится ставить переменные, согласовывать с дизайнером, дублировать в Storybook.
Что делает clamp()
Синтаксис: clamp(min, preferred, max). Значение растёт по формуле preferred, но не выходит за min и max.
h1 {
font-size: clamp(24px, 4vw, 48px);
}Здесь шрифт всегда не меньше 24px, не больше 48px. Между этими значениями — растёт пропорционально ширине viewport (4% от ширины экрана). На 600px viewport вычислится 24px (упёрся в min). На 800px вычислится 32px. На 1200px — 48px (упёрся в max). Между ними — плавно.
Никаких breakpoints. Никаких медиазапросов. Один тег для каждого размера шрифта в дизайн-системе.
Проблема vw как preferred
Использовать vw напрямую — соблазнительно, но опасно. 4vw на широком мониторе 2560px даёт 102px. 4vw на узком 320px даёт 12.8px. Растёт линейно от viewport, а это чаще всего не то, что нужно.
Дизайнеру нужна не «линейная зависимость от viewport», а «вот тут на маленьком 16px, вот тут на большом 24px, посередине плавно». То есть нужно вычислить такую формулу a + b*vw, чтобы:
- На минимальном viewport (например, 320px) получалось 16px.
- На максимальном viewport (например, 1280px) получалось 24px.
Эта система из двух уравнений решается за пять минут. Но руками каждый раз — глупо.
Формула для clamp()
Если хочешь, чтобы значение было v1 на ширине w1 и v2 на ширине w2:
font-size: clamp(
v1px,
((v1 * w2 - v2 * w1) / (w2 - w1))px + ((v2 - v1) * 100 / (w2 - w1))vw,
v2px
);Запоминать не надо. Подставляешь числа в калькулятор. Я обычно использую Utopia, она генерирует готовые clamp-формулы по двум точкам.
Пример: текст должен быть 16px на ширине 320px и 24px на ширине 1280px. Подставляем:
font-size: clamp(
1rem,
0.83333rem + 0.83333vw,
1.5rem
);На 320px вычислится 16px, на 1280px — 24px, между — плавно растёт.
Шкала шрифтов на clamp()
В дизайн-системе на 200+ компонентов я завожу не отдельные размеры в каждом месте, а единую шкалу токенов. Шесть-восемь размеров, каждый — clamp с min/max:
:root {
--font-size-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--font-size-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
--font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--font-size-md: clamp(1.125rem, 1rem + 0.625vw, 1.375rem);
--font-size-lg: clamp(1.25rem, 1rem + 1.25vw, 1.75rem);
--font-size-xl: clamp(1.5rem, 1.1rem + 2vw, 2.5rem);
--font-size-2xl: clamp(2rem, 1.25rem + 3.75vw, 3.5rem);
--font-size-3xl: clamp(2.5rem, 1.5rem + 5vw, 4.5rem);
}В компоненте используешь только токен:
.heading-l {
font-size: var(--font-size-xl);
line-height: 1.2;
}
.text-body {
font-size: var(--font-size-base);
line-height: 1.5;
}Меняется поведение шкалы — правишь в одном месте.
Line-height тоже clamp
Многие забывают: line-height тоже желательно адаптировать. На большом шрифте плотный line-height (1.1-1.2) выглядит хорошо, на мелком тексте нужен 1.4-1.5 для читаемости.
Проще всего использовать unitless line-height (просто число), потому что оно автоматически умножается на font-size. Но если нужна точная адаптация, можно завести clamp:
--line-height-tight: clamp(1.1, 1.05 + 0.1vw, 1.2);
--line-height-base: 1.5; /* для основного текста — фиксированное число */Container query units: следующий уровень
В 2026 в Chromium и Safari есть cqi — единица, равная 1% от inline-size ближайшего контейнера. С ней можно делать типографику, адаптирующуюся не к viewport, а к ширине компонента.
.card {
container-type: inline-size;
}
.card__title {
font-size: clamp(1.125rem, 0.9rem + 1.5cqi, 1.5rem);
}Карточка в широком слоте получит крупный заголовок, в узком — компактный. Без media queries, без вариантов компонента в JS.
Что не делать
Не используй clamp для всего
clamp хорош там, где нужна плавная адаптация значения. Для бинарных переключений (показать/скрыть элемент, поменять направление флекса) — это всё ещё @media или @container.
Не забывай rem вместо px
Если в clamp ставишь 16px, ты ломаешь масштабирование шрифтов в браузере. Пользователь, у которого в настройках выставлен крупный шрифт, не получит увеличения. Используй rem:
/* плохо */
font-size: clamp(16px, 0.83rem + 0.83vw, 24px);
/* хорошо */
font-size: clamp(1rem, 0.83rem + 0.83vw, 1.5rem);1rem по умолчанию равен 16px, но пользователь может это перенастроить — и шрифты должны под это подстраиваться.
Не задавай узкие пороги min-max
Если у тебя clamp(20px, 2vw, 22px), шрифт практически всегда будет 20 или 22 — диапазон, в котором он растёт пропорционально, очень узкий. Лучше сделать min/max шире, например clamp(18px, 2vw, 28px).
Не забывай про доступность
Самое маленькое значение в clamp должно оставаться читаемым. WCAG рекомендует основной текст не меньше 16px. Никаких clamp(12px, ...) на body — это слишком мелко на мобиле.
Тестирование
Просто открой свою страницу в браузере и плавно меняй ширину окна от 320px до 1920px. Шрифты должны плавно расти, без скачков. Если есть скачок — где-то не clamp, а медиазапрос.
В Chrome DevTools я держу режим Device Toolbar и быстро прокручиваю responsive-режим. На больших шрифтах сразу видно, если что-то на грани читаемости.
Полезный лайфхак: в DevTools на вкладке Rendering есть «Disable user-agent fonts». Не имеет отношения к clamp, но в комплекте с проверкой адаптивности — удобно.
Итоговый снапшот
За год работы по этой схеме у меня в дизайн-системе живёт ровно 8 токенов размера шрифта, каждый — один clamp(). Никаких media queries для типографики. Никаких overrides в компонентах. Storybook рендерит всю шкалу на одной странице, и дизайнер видит результат на разных ширинах сразу.
Когда возникает вопрос «хочется на мобиле сделать h1 поменьше» — это не правка десяти медиазапросов в десяти компонентах. Это правка одного значения в одном файле. Я готова кодить ради таких вещей.
Что унести с собой
clamp() в типографике — это не просто «удобнее, чем media queries». Это другой способ мышления: ты задаёшь не серию состояний для разных breakpoints, а функциональную зависимость размера от ширины контекста.
Минимум для старта: возьми текущую шкалу шрифтов в проекте, для каждого размера вычисли clamp по двум точкам (min и max viewport, в которых ты хочешь видеть свои размеры), подставь в CSS-переменные. Удали все @media с правками font-size — они больше не нужны. Профит — плавная адаптация без скачков и легко поддерживаемый CSS.