Дайджесты новостей
Крупный план типографической разметки многострочного текста с выравниванием иконки-маркера по оптическому центру первой строки через единицу lh.

Единица lh в CSS: идеальное выравнивание иконок по первой строке текста

Спроси любого верстальщика или фронтенд-разработчика о том, какая мелочь в интерфейсе способна довести до тихого бешенства, и тебе наверняка назовут выравнивание иконки в списке с текстом. В теории всё выглядит элементарно: берём родительский контейнер, включаем Flexbox, ставим иконку слева, текстовый блок справа. Если подпись короткая и умещается в одну аккуратную строчку, свойство align-items: center даёт безупречную картинку. Иконка и текст идеально сбалансированы по общей горизонтальной оси.

Но веб-интерфейсы не живут в стерильном мире коротких строк. Стоит пользователю уменьшить окно браузера, открыть страницу на смартфоне или переключить язык интерфейса на немецкий, как текстовая подпись тут же разворачивается на две, три или четыре строки. И в этот момент вся вёрстка начинает безжалостно сыпаться. Ранее мы подробно исследовали смежные приёмы работы с графикой в материале Иконки на чистом CSS: техника Ли Веру с кастомными свойствами и масками.

Почему стандартный Flexbox бессилен при переносе строк

В попытках обуздать многострочный список разработчики обычно мечутся между двумя стандартными значениями выравнивания во Flexbox:

  1. Ловушка центра (align-items: center): Контейнер центрирует иконку относительно суммарной физической высоты всего блока текста. Если в абзаце набралось четыре строки, иконка съезжает куда-то в район между второй и третьей строками. Теряется фундаментальная визуальная связь: глаз читателя ищет маркер в начале предложения, а находит его сиротливо висящим посреди текстового массива.
  2. Ловушка верхней кромки (align-items: start или flex-start): Элементы прижимаются к верхнему краю строки. Но верхняя граница текстового контейнера определяется выносными элементами прописных букв (cap-height и ascenders). В итоге иконка прилипает к самой верхней кромке строки, тогда как оптический центр первой строки находится ощутимо ниже — примерно посередине высоты строчных букв (x-height).
  3. Хрупкие костыли с «магическими числами» (Magic Numbers): Отчаявшись, инженер прописывает margin-top: 3px или transform: translateY(2px). На рабочем мониторе в Chrome это выглядит сносно. Но стоит пользователю изменить системный масштаб шрифта, включить режим увеличенного текста, сменить гарнитуру или открыть сайт в другой операционной системе с иными метриками рендеринга шрифтов — и маркер снова не попадает в строку.

Анатомия единицы lh и корневой rlh

Элегантное решение этой многолетней проблемы появилось в официальной спецификации рабочих стандартов W3C — CSS Values and Units Module Level 4. В спецификацию вошли новые относительные типографические единицы: lh и rlh.

  • Единица lh (line-height) равна вычисленному значению свойства line-height того элемента, к которому она применяется. Если у абзаца задан кегль font-size: 16px и относительный интерлиньяж line-height: 1.5, то полная высота строки составляет 24 пикселя. Соответственно, значение 1lh внутри этого элемента будет строго равно 24px.
  • Единица rlh (root line-height) работает аналогично единице rem, привязываясь к вычисленной высоте строки корневого элемента страницы (:root или <html>).

В чём фундаментальная разница между привычной единицей em и новой lh? Единица em привязана исключительно к размеру кегля шрифта (font-size), полностью игнорируя межстрочный интервал (leading). Если дизайнер увеличит line-height с 1.2 до 1.6 для лучшей читаемости длинных текстов, все расчёты на базе em немедленно разойдутся. Единица lh динамически отслеживает честную высоту строки со всеми полукегельными отступами. Другие свежие возможности стандарта мы разбирали в обзоре Тренды CSS 2026.

Геометрия Шадида: формула центрирования через translateY

Фронтенд-архитектор Ахмад Шадид (Ahmad Shadeed) предложил использовать свойства геометрии строки. Вместо слепого подбора пикселей мы вычисляем оптический центр через чистую математику:

  1. Родитель списка выравнивает дочерние элементы по верхнему краю: display: flex; align-items: start;.
  2. Высота первой строки текста нам теперь точно известна — она равна 1lh.
  3. Сама иконка имеет фиксированную или относительную высоту, которую мы сохраняем в CSS-переменную: --size: 20px.
  4. Свободное пространство внутри первой строки текста вокруг иконки составляет (1lh - var(--size)).
  5. Чтобы иконка встала точно по центру высоты первой строки, это свободное пространство нужно разделить поровну между верхом и низом.

Отсюда рождается простая формула смещения через функцию calc():

.list-icon {
  --size: 20px;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  /* Смещаем иконку ровно в оптический центр первой строки */
  transform: translateY(calc((1lh - var(--size)) / 2));
}

Смотри, как эта математика ведёт себя на практике:

  • Если высота строки 1lh равна 24px, а иконка 20px, формула даёт (24 - 20) / 2 = 2px смещения вниз. Иконка встаёт ровно по центру текста.
  • Если шрифт увеличивается пользователем до 24px при том же коэффициенте 1.5, высота 1lh автоматически становится 36px. Формула моментально выдаёт (36 - 20) / 2 = 8px.
  • Если декоративный маркер крупнее текста (например, бейдж 28px при высоте строки 24px), результат (24 - 28) / 2 = -2px приподнимет элемент вверх.

Готовые рецепты: Inline SVG и маски псевдоэлементов

Техника одинаково надёжно работает как для прямых SVG-элементов в HTML-разметке, так и для фоновых иконок через псевдоэлементы ::before.

Вот базовый рабочий шаблон для компонента списка с векторной иконкой:

<ul class="checklist">
  <li class="checklist-item">
    <svg class="checklist-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
    </svg>
    <span class="checklist-text">
      Многострочный пункт списка, в котором текст свободно переносится на несколько строк при изменении ширины вьюпорта или пользовательском масштабировании.
    </span>
  </li>
</ul>

А вот полный блок CSS-стилей для списка с защитой от сжатия маркера:

.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.checklist-item {
  display: flex;
  align-items: start;
  gap: 0.75rem;
  font-size: 1rem;
  line-height: 1.5;
}

.checklist-icon {
  --size: 1.25rem;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  color: #2563eb;
  transform: translateY(calc((1lh - var(--size)) / 2));
}

Если ты предпочитаешь держать HTML чистым от лишних служебных тегов, ту же логику легко перенести на псевдоэлемент с техникой CSS-масок (mask-image). Псевдоэлемент красится цветом текста через currentColor, а формула translateY остаётся прежней:

.feature-item::before {
  content: "";
  --size: 1.25rem;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  background-color: currentColor;
  mask: url("data:image/svg+xml,...") no-repeat center / contain;
  -webkit-mask: url("data:image/svg+xml,...") no-repeat center / contain;
  transform: translateY(calc((1lh - var(--size)) / 2));
}

Поддержка браузерами и прогрессивный fallback

По данным сервисов Can I use и MDN Web Docs, единица lh официально включена в набор Baseline и доступна во всех актуальных движках:

  • Google Chrome и браузеры на базе Chromium / Edge поддерживают lh начиная со 109 версии (январь 2023 года);
  • Safari на движке WebKit поддерживает единицу начиная с версии 16.4 (март 2023 года);
  • Mozilla Firefox добавил полную поддержку в релизе 120 (ноябрь 2023 года).

Если твоё приложение вынуждено поддерживать устаревшие корпоративные браузеры, используй директиву прогрессивного улучшения @supports:

/* Базовый откат для устаревших движков */
.checklist-icon {
  --size: 1.25rem;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  transform: translateY(0.15em);
}

/* Прогрессивное улучшение с точной формулой lh */
@supports (height: 1lh) {
  .checklist-icon {
    transform: translateY(calc((1lh - var(--size)) / 2));
  }
}

Такой подход навсегда закрывает проблему съехавших маркеров и превращает вёрстку списков в предсказуемую инженерную систему.