lenec ru

← все посты

Адаптивная типографика на clamp() без JavaScript

10K

Когда я в первый раз увидела 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.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.