Daily MaverickEx-Google DeepMind researcher adds to warnings that AI could ‘kill all humans’וואלהצה"ל חיסל את מפקד הפלוגה במטה המבצעים בחמאס, יחד מחבל נוסףESPNCeltics offseason recap and early-season preview: Tatum returns as the catalystRTP Desporto12h30 Benfica a 100% para Amorim, Ramos e Diego MoreiraPunchKenya to host 2029 World Athletics Championships in African firstThe Jerusalem PostPolice investigating threats against prominent Munich Holocaust survivor and AfD opponentInquirerWATCH: Ombudsman lawyer takes the witness standCollider'Super Troopers 3' Officially Rides Onto Digital With New Blooper Reel [Exclusive]SCMP ChinaChina mulls building nuclear-powered tank with 450km-range railgun in 2 decadesSouth China Morning PostChina bets on chips and AI in new 5-year road map to challenge US tech dominanceBusiness AMNieuwe nucleaire raket Sentinel bereikt belangrijke mijlpaal en is klaar voor testvlucht in 2027VarietySamsung’s Galaxy Watch Ultra2 Is Up to $250 Off With This Special Trade-In Deal
The Daily Newsstand · Free, Always
Tuesday, September 15, 2026

Задача со звёздочкой для фронтенда: выравниваем иконки с текстом

Translate

Расположить иконку рядом с текстом — с этой задачей сталкивался каждый фронтенд-разработчик. Вроде бы ничего сложного.

Тогда почему с ней не могут справиться ни Apple, ни Microsoft, ни Google, ни другие техногиганты? 

Меня зовут Армен Аллахвердян, я фронтенд-разработчик из команды дизайн-системы Авито. В статье разберёмся, почему простая на первый взгляд задача вызывает столько проблем, и выясним, как надо и как не надо выравнивать иконки.

Содержание

Проблема?

Стандартная ситуация: добавляем в текст иконку — и она встаёт криво.

<p>
    Нравится 
    <svg ... />
</p>

Критерии

Сначала договоримся, что считать правильно выровненной иконкой. Задам три критерия:

  1. Иконка воспринимается глазом так, будто она выровнена по центру. С реальным центром это может быть никак не связано.

  2. Размер иконки гармонично встраивается в окружение.

  3. Иконка не раздувает высоту строки и не ломает вертикальный ритм в многострочном тексте.

Тут еще больше контента

Тут еще больше контента

По ходу статьи разберёмся, как этого добиться и соберём небольшой чек-лист для самопроверки при работе с иконками. А теперь к проблеме.

Из чего складывается высота строки

У Vincent De Oliveira есть подробная статья про метрики шрифта, line-height и vertical-align — «Deep dive CSS: font metrics, line-height and vertical-align» (есть перевод на Хабре). Рекомендую, если захотите разобраться глубже. Здесь же обсудим только то, что нужно для общего понимания.

Чтобы понять, как браузер рассчитывает высоту строки, нужно рассмотреть три величины.

Первая — content-area: область, которую занимает сам текст: от верхней границы букв до нижней. Увидеть её просто: задайте тексту background. Закрашенная зона и есть content-area. Размер этой зоны задают метрики шрифта. У каждой гарнитуры они свои, поэтому при одинаковом font-size разные шрифты будут иметь разную высоту. De Oliveira в своём разборе сравнил три шрифта при font-size: 100px. Helvetica заняла 115 пикселей, Catamaran — 164, а Gruppo — всего 97. Пощупать это вживую можно в демо на CodePen.

Откуда берётся разница? Шрифт описывается в собственной координатной сетке — em-square; её размер задаёт параметр unitsPerEm. Когда мы пишем font-size: 100px, браузер масштабирует до 100 пикселей именно em-square. Но шрифт не обязан вписываться в этот квадрат: его реальная высота может оказаться больше. У Catamaran em-square равен 1000 юнитов, а реальная высота шрифта — 1640 (ниже станет понятно, откуда берутся такие числа). При font-size: 100px эти 1640 юнитов превращаются в 164 пикселя.

В Авито мы используем собственный шрифт Avito Sans. При font-size в 100 пикселей его content-area составит 137 пикселей.

Вторая — line-height. Этому термину на удивление тяжело дать точное определение: даже спецификация не говорит, что это такое, — только для чего используется. По сути line-height — это свойство, которое браузер использует для расчёта высоты строки. Значение line-height может быть меньше content-area, больше или они могут совпадать. Разница между ними делится пополам и добавляется сверху и снизу content-area. Это называется leading.

По умолчанию line-height равен normal: это значение браузер вычисляет из метрик шрифта, и у каждой гарнитуры оно своё. Частая ошибка — считать, что normal равен единице. В 2017 году De Oliveira поставил себе все шрифты с Google Fonts — тогда их было 1117 — и посчитал: у 95% шрифтов normal больше единицы, а значения лежат в диапазоне от 0,618 до 3,378. Я повторил его эксперимент на актуальной базе из 1823 шрифтов: результат практически не изменился, normal оказался больше единицы у 95,8% шрифтов, медиана составила 1,3.

Если задать line-height: 1, высота строки будет равна font-size, но content-area, как мы теперь знаем, может быть значительно больше. Тогда leading становится отрицательным. Браузер по-прежнему рисует глифы целиком, поэтому при отрицательном leading соседние строки могут налезать друг на друга.

Третья — line-box: фактическая высота строки. Считается она от самой верхней до самой нижней границы всех элементов в строке. Напрямую повлиять на неё нельзя ни через CSS, ни через JS; даже увидеть её в DevTools не выйдет.

Одно правило: line-box не может быть меньше line-height.

Чтобы увидеть эти величины на одной картинке, увеличим line-height до 200 пикселей.

font-size: 100px · content-area: 137px · line-height: 200px · line-box = line-height

font-size: 100px · content-area: 137px · line-height: 200px · line-box = line-height

Сверху и снизу появилась голубая зона — это разница между line-height и content-area, поделённая пополам: (200 − 137) / 2 = 31,5 пикселя с каждой стороны. Здесь line-box совпадает с line-height: кроме текста, в строке ничего нет, растягивать её нечему.

Теперь добавим в строку иконку. Это inline-элемент, поэтому по умолчанию она встаёт на baseline.

font-size: 100px · content-area: 137px · line-height: 200px · line-box: 250px · красный пунктир — границы иконки

font-size: 100px · content-area: 137px · line-height: 200px · line-box: 250px · красный пунктир — границы иконки

Высота иконки больше расстояния от базовой линии до верха строки, поэтому line-box вырос до 250 пикселей: на картинке это тёмно-синяя зона. Строка стала выше, чем задаёт line-height, и в многострочном тексте такие скачки ломают вертикальный ритм.

Метрики шрифта

Выше я говорил, что размер content-area задают метрики шрифта. Их очень много, но нас интересуют пять: baseline, ascender, descender, x-height и cap-height. Если захочется поразбираться с остальными, можно зайти на fontdrop.info, загрузить любой шрифт и развлекаться.

Точка отсчёта — базовая линия, baseline: от неё отсчитываются все вертикальные расстояния в шрифте. Ниже неё опускаются только нижние выносные элементы, например хвостик буквы p, да знаки вроде запятой.

Вниз от baseline — только одна метрика, descender: максимальная высота нижних выносных элементов. Это нижняя граница content-area, и задана она почти всегда с запасом.

Над baseline 3 метрики. Ближайшая — x-height: высота строчных букв без верхних выносных элементов. Названа по строчной x, по которой её обычно и считают.

Следующая — cap-height: высота заглавных букв, чаще всего считается по H. Обычно это примерно 70% от font-size.

Верхняя — ascender: максимальная высота верхних выносных элементов строчных букв, например хвостика d. На картинке видно, что хвостик поднимается выше заглавной H: у многих шрифтов верхние выносные обгоняют cap-height. Как и descender, ascender почти всегда задаётся с запасом.

Два вывода.

Расстояние от ascender до descender — это и есть content-area.

Все эти метрики постоянны для каждого шрифта и никак не меняются в зависимости от содержимого строк. Единственная оговорка — вариативные шрифты: там метрики могут отличаться между начертаниями (Regular, Bold и так далее).

Любопытный (но не самый приятный) факт об устройстве шрифтов. Метрики не меняются от содержимого — но что если я скажу, что в разных браузерах и операционных системах одни и те же метрики могут отличаться?

Наши шрифты хранятся в формате OpenType. WOFF2 и прочие — не отдельные форматы шрифта, а контейнеры: они отвечают за сжатие и дополнительные метаданные.

В OpenType лежит куча табличек с разными метаданными шрифтов, но нас интересуют всего две вещи:

  • OS/2 — эту таблицу по умолчанию читает Windows;

  • hhea — а эту читают macOS, iOS, Linux.

Эти таблицы частично дублирют друг друга. При этом метрики там могут отличаться — например, тот же ascender может в разных таблицах иметь разные значения. Причины разные: ошибка дизайнера при разработке шрифта или последствия адаптации под другую платформу.

Насколько это распространено? Я прогнал по таблицам те же 1823 шрифта Google Fonts. У 62% шрифтов хотя бы одна метрика в OS/2 и hhea записана с разными значениями.

До экрана большинство расхождений не доезжает: обычно в шрифте выставлен специальный флаг, с которым и Windows, и macOS используют одни и те же значения. Но у 5% шрифтов line-height: normal на macOS и Windows отличается. Чаще всего разница меньше пикселя на строку при кегле 16, и её никто не заметит, но встречаются шрифты с разницей почти вдвое.

Если хотите объяснить дизайнерам, что pixel perfect не существует — покажите им это.

Для нас же это означает, что метрики, из которых браузер считает line-height, в разных операционных системах могут отличаться для некоторых шрифтов, а с ними и финальный результат в интерфейсе. Что с этим делать? Выбирать нормальный шрифт :D. Или хотя бы задайте line-height явно: тогда высота строки перестанет зависеть от таблиц.

Жми сюда!

Жми сюда!

Как не надо выравнивать иконки

Рассмотрим методы, которые первыми приходят на ум, но не дадют стабильного результата.

vertical-align

Как вообще работает vertical-align? Каждое его значение выравнивает элемент по своему ориентиру, и почти все ориентиры собраны из метрик шрифта, которые мы разобрали выше: где-то baseline, где-то x-height, где-то ascender с descender. Исключение — top и bottom: их ориентир line-box. Это скорее метрика строки — сам шрифт такой не имеет. Вдобавок свойство участвует в расчёте высоты line-box: вместе с положением элемента меняется и высота строки вокруг него.

В строке по умолчанию все SVG встают на базовую линию — как на примере ниже.

Контейнер иконки — прямоугольник, в который вписан её рисунок; у svg его задаёт атрибут viewBox. Контейнер почти всегда больше самого рисунка. Этот контейнер встал на базовую линию и растянул высоту строки, как видно по голубой линии. Что случится в многострочном тексте?

Заметно сломался вертикальный ритм

Заметно сломался вертикальный ритм

Между первой и второй строкой расстояние намного больше, чем между второй и третьей. Выше мы уже разобрали этот пример: иконка встаёт на baseline и растягивает line-box. Поэтому не стоит полагаться на выравнивание по базовой линии для иконок.

Может, с другими значениями повезёт больше?

middle: baseline + x-height / 2

Я часто сталкивался с мнением, что middle выравнивает элемент посередине строки, но это неверно. middle ставит середину элемента на уровень baseline + половина x-height.

top / bottom

Выравнивают элементы по верху или низу line-box. Ориентир — вся строка целиком: если в ней появится другой высокий элемент, иконка выровняется по нему, а не по тексту рядом.

text-top / text-bottom

Выравнивают по верху или низу content-area — то есть по ascender и descender, у которых, как мы помним, почти всегда есть запас.

Голубая зона — line-height: в этом примере он совпадает с line-box. top и bottom выровнены по line-box, text-top и text-bottom — по content-area.

Голубая зона — line-height: в этом примере он совпадает с line-box. top и bottom выровнены по line-box, text-top и text-bottom — по content-area.

Как это будет выглядеть с иконками? Вот так.

Живой пример

Живой пример

У vertical-align не нашлось ключевого значения, которое выравнивало бы иконку c текстом так, чтобы соответствовать заданным критериям. 

Магические числа

После безуспешных попыток добиться результата с помощью vertical-align в ход часто идут магические числа. Можно ведь подобрать отступ на глаз, что может случиться? Выглядит это как-то так:

.icon {
  vertical-align: baseline;
  margin-bottom: -4px;
}.

Иконка на baseline смотрится слишком высоко, и её опускают отрицательным отступом, подобранным на глаз. Почему это плохо? −4px — это число, которое эмпирически выведено для конкретной гарнитуры, конкретного кегля и конкретного размера иконки. Если что-нибудь из этого изменится, иконка будет выглядеть криво. Конечно, в этом случае можно заменить магические -4px на, например, магические -6px. Но надолго ли этого хватит?

Другой пример:

.icon {
  vertical-align: top;
  transform: translateY(-37%);
}

Почему этот пример плох? Предлагаю сначала подумать самостоятельно, а затем заглянуть под спойлер.

Почему это плохо

Во-первых, top — это край line-box, который может меняться в зависимости от метрик шрифта, line-height и содержимого строки, что само по себе не очень надёжно. Например, если вырастет line-height или в строке появится сосед повыше — иконка сместится вверх, хотя текст останется на месте. Во-вторых, −37% — это проценты от высоты самой иконки, посчитанные под конкретный размер в конкретной ситуации. Любое изменение, влияющее на высоту строки или иконки, потребует пересчёта этого значения.

Примеров накидать можно ещё много, но, думаю, суть понятна. Любые сдвиги «на глаз» ломаются от малейших изменений, полагаться на них не стоит.

Подбирать числа самим, тем более абсолютные — сложно и, как мы выяснили, работает так себе. Но может кто-то уже нашёл решение за нас? 

В интернете есть довольно распространённый «лайфхак», который иногда продают как универсальное решение для выравнивания иконок. Выглядит он так:

.icon {
    vertical-align: -0.125em;
}

На самом деле его универсальность весьма ограничена. Для тех, кому интересно было бы погрузиться в лор — смотрите спойлер!

Откуда взялось 0.125em

Скорее всего, впервые это число было упомянуто в январе 2017 года в статье Elliot Dahl «Align SVG Icons to Text and Say Goodbye to Font Icons». Он показывал, как заставить SVG-иконки вести себя подобно иконочным шрифтам. Иконки у него рендерились ровно в кегль, 1em на 1em, но на базовую линию садились слишком высоко: при кегле 48px до настоящей baseline оставалось 6px. Даль опустил их ровно на эту разницу — 6/48, восьмая часть кегля. Так родились -0.125em. Универсальности он, к слову, не обещал. По сути за хаком не стоит ни одна метрика шрифта — только эмпирический расчёт для конкретного набора иконок под конкретную гарнитуру.

Потом, в конце 2017 года, вышла 5 версия библиотеки Font Awesome, в которой vertical-align: -0.125em сделали дефолтом для всех SVG-иконок. А уже оттуда «магическое» число разошлось по интернету как «база». Bootstrap Icons потом тоже добавил этот хак себе, прямо сославшись на «проверенный временем Font Awesome».

Чтобы хак работал, должны быть выполнены два условия:

  1. Размер иконки должен быть 1em. При изменении высоты иконки число нужно пересчитывать.

  2. Метрика cap-height у шрифта должна быть равна 0,75em. Это условие выполняется для многих шрифтов, но далеко не для всех.

Смещение на -0,125em перестаёт корректно работать даже при разнице в несколько сотых em у cap-height.

Чем больше кегль, тем сильнее это заметно:

cap-height

Промах при кегле 16

Промах при кегле 64

0,75em

0

0

0,72em

0,24px

0,96px

0,7em

0,4px

1,6px

0,66em

0,7px

2,9px

Промах — расстояние между центром иконки и серединой заглавных.

Я подготовил небольшую песочницу, где можно поиграться с разными шрифтами и на практике разобраться, как это работает.

Конечно, можно подобрать правильное смещение под любой случай. Вопрос только в том, как долго это проживёт?

TLDR или вывод: почему магические числа ломаются

Такие числа могут быть подобраны только под конкретный узкий кейс. Чтобы всё сломалось даже не обязательно менять гарнитуру. В рамках одного семейства шрифтов у его начертаний метрики могут отличаться. Отдельный случай — адаптивная типографика, там в зависимости от подхода и ширины экрана могут меняться и кегль, и line-height, и начертание. Поверьте, даже сдвиг на 1 пиксель может быть визуально заметным и бить по чьему-то чувству прекрасного.

flexbox

Когда нужно выровнять несколько элементов по центру, обычно используют flexbox. 

<p class="paragraph">
  <span>Нравится</span>
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"></svg>
</p>
.paragraph {
    displ
ay: flex;
    align-items: center;
}

Почему это плохая идея? Вспомним первый критерий корректно выровненной иконки: 

Иконка воспринимается глазом так, будто она выровнена по центру.

В примере выше иконка встанет на математический центр — середину строчного элемента. Но математический центр не всегда совпадает с тем центром, который видит наш глаз. 

Есть ещё парочка неприятных нюансов. Во-первых, flexbox по умолчанию накидывает потомкам flex-shrink: 1, и при нехватке места иконка начнёт сжиматься. Во-вторых, что хуже: если текст не влезет и перенесётся на вторую строку, центр абзаца изменится, и чиконка окажется между строк. Можете поиграться с этим в CodePen.

В общем, в большинстве случаев я бы не стал использовать flexbox для нашей задачи.

text-box-trim и text-box-edge

Относительно новые CSS-свойства text-box-trim и text-box-edge могут частично решить проблемы flexbox.

text-box-trim позволяет убирать лишние вертикальные отступы над и под текстом, а text-box-edge задаёт, по каким визуальным линиям текста нужно резать. 

Немного изменим пример с флексом:

.div {
  display: flex;
  align-items: center;
}
 
.div > span {
  text-box: trim-both cap alphabetic;
}

trim-both велит резать с обеих сторон, cap alphabetic задаёт края: сверху по cap-height, снизу по baseline. Вешать свойство нужно прямо на элемент с текстом: оно не наследуется. После обрезки надпись занимает ровно зону от базовой линии до верха заглавных. Воздух, из-за которого центры расходились, отрезан целиком — и align-items: center впервые попадает туда, куда смотрит глаз. Выходит, флекс всё это время врал не про центрирование — врала строка, которую он центрировал.

Но это выглядит как хак для ряда частных случаев, который я бы не стал активно использовать. Во-первых, поддержка совсем свежая: Chrome научился работать с ними в 2025 году, Safari — в конце 2024-го, а Firefox только в августе 2026-го. Во-вторых, text-box стрижёт текстовый блок, что влечёт за собой уйму проблем: может сломаться вертикальный ритм, отступы между элементами типографики поедут, а в многострочном тексте вообще обрезаются только верх первой строки и низ последней.

Кликни здесь и узнаешь

Кликни здесь и узнаешь

А теперь к главному...

Как надо выравнивать иконки

У всех решений выше по сути одна проблема: нет стабильного ориентира, который отвечал бы нашим критериям корректно выровненной иконки. Нужен ориентир, который считается из метрик шрифта и совпадает с тем, что глаз считает серединой текста. И такой ориентир есть! Это оптический центр строки. 

Как бы ни менялась строка, оптический центр у неё будет всегда, и посчитать его несложно: для строки текста это середина между baseline и cap-height.

Красная линия — оптический центр строки для человеческого глаза

Красная линия — оптический центр строки для человеческого глаза

Математический центр строки у большинства шрифтов не совпадает с оптическим. Совпадают они только у шрифтов, где ascender — cap-height = descender, то есть «воздух» над заглавными буквами равен «воздуху» под базовой линией. Таким шрифтам хватает обычного центрирования, даже тем же флексом (впрочем, его проблемы со сжатием и переносом строк никуда не исчезают). Но подавляющее большинство шрифтов устроено иначе, поэтому оптический центр мы посчитаем напрямую, из baseline и cap-height:

--optical-center: calc(1cap / 2);

cap здесь — CSS-юнит, равный cap-height текущего шрифта: половина высоты заглавных над базовой линией и есть оптический центр.

Осталось правильно расположить иконку, и тут есть два способа. 

Первый — для тех, кто работает с публичными библиотеками иконок или вовсе без библиотек: ставим иконку формулой на вычисленный оптический центр. Подходит для любых иконок. 

Второй — для тех, кто строит собственную библиотеку иконок под свою дизайн-систему: компенсация закладывается в иконку ещё на этапе отрисовки. 

Разберём оба.

Способ 1: оптический центр по формуле

Берём иконку оборачиваем её в span. Важно: span должен быть пустым, без единого символа внутри.

<span class="outer-span">
  <svg class="icon" />
</span>

Накидываем такие стили:

.outer-span {
  --aspect-ratio: 0.8;
 
  position: relative;
  display: inline-block;
  /* Значение по умолчанию, но лучше зафиксировать явно */
  vertical-align: baseline;
  /* Высота иконки умножается на соотношение сторон */
  width: calc(1em * var(--aspect-ratio));
}

Пройдёмся по стилям. 

  1. display: inline-block — без него span не примет ширину;

  2. position: relative — чтобы абсолютно спозиционировать иконку внутри;

  3. --icon-height: 1em — в примере высота иконки 1em, но подойдёт любая.

Дальше начинается интересное — width. Иконка внутри спозиционирована абсолютно и вырвана из потока. Из-за этого внутри span не остаётся in-flow элементов, и его ширина и высота схлопываются в ноль — текст начинает наезжать прямо на иконку. Чтобы это исправить, ширину задаём явно: высота иконки, умноженная на соотношение сторон — на случай, если иконка не квадратная.

Теперь к самой иконке:

.icon {
  position: absolute;
  height: 1em;
  left: 0;
  /* Из высоты заглавных букв вычитается высота иконки. Полученное значение делится на 2 */
  bottom: calc((1cap - 1em) / 2);
}

Задали ей высоту, абсолютно спозиционировали, а выравнивать будем по bottom. Почему именно по bottom? 

CSS-спецификация гласит:

Базовая линия элемента с display: inline-block — это базовая линия его последнего line box в нормальном потоке. Если же line box в потоке у него нет, базовой линией считается нижний край его margin-box.

Оригинал

The baseline of an inline-block is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its overflow property has a computed value other than visible, in which case the baseline is the bottom margin edge.

У .outer-span высота равна 0, потому что единственный вложенный элемент — иконка — вырван из потока. Поэтому все его границы находятся на baseline родительской строки. От неё смещение иконки и отсчитывается.

Остаётся посчитать само смещение:

(высота заглавных букв − высота иконки) / 2 = смещение низа иконки от базовой линии

При таком bottom центр иконки попадает ровно в оптический центр.

Подставим числа: возьмём Avito Sans при кегле 15px, его cap-height — 0,72em:

1em  = 15px
1cap = 0.72 × 15px = 10.8px
bottom = (10.8 - 15) / 2 = -2.1px
Пустая обёртка лежит на baseline, контейнер иконки опущен на (1cap − высота) / 2, и центр иконки попадает на оптический центр строки

Пустая обёртка лежит на baseline, контейнер иконки опущен на (1cap − высота) / 2, и центр иконки попадает на оптический центр строки

Иконка выше заглавных букв, поэтому должна располагаться чуть ниже baseline. Выглядит это так:

Красная линия — baseline, синяя — оптический центр строки; центр иконки лежит на синей

Красная линия — baseline, синяя — оптический центр строки; центр иконки лежит на синей

И без линий:

Теперь иконка выровнена по оптическому центру, и строка сразу смотрится приятнее. 

А живой пример можно посмотреть тут.

К сожалению, это решение не идеально. Во-первых, оно не учитывает форму конкретной иконки. У сердечка с картинок выше верх визуально тяжелее, поэтому может казаться, что иконка выше, чем на самом деле. Во-вторых, контейнер иконки, в который вписан её рисунок, тоже может влиять на визуальное восприятие. Где-то контейнер обтягивает глиф, где-то нарисован с воздухом — и при одинаковых вводных иконки из разных наборов выглядят совершенно по-разному. Второй способ позволяет эти проблемы решить.

Способ 2: иконки, нарисованные под типографику

Чтобы иконка идеально вписывалась в ваш продукт, она должна знать о нём немного больше. Как это работает:

  1. Берём строку текста из ДС. 

  2. Рисуем контейнер иконки с высотой, равной высоте строки.

  3. Вписываем в этот контейнер иконку таким образом, чтобы она выглядела оптимально.

В дизайн-системе Авито иконки устроены именно так.

Разметка почти та же, только span больше не пустой — в нём должен находиться любой невидимый символ, например, полупробел:

<span class="outer-span">
  &ensp;
  <svg class="icon" />
</span>

Символ нужен, чтобы у span появился in-flow контент: с ним span начинает наследовать line-height и другие метрики от родительской строки.

В CSS ширину больше не нужно считать формулой: у span есть контент, а значит и размер, — хватает aspect-ratio, остальное браузер сделает сам. А высота иконки теперь задаётся юнитом lh, равным вычисленному line-height строки, то есть иконка растягивается ровно на высоту строки.

.outer-span {
  display: inline-block;
  position: relative;
  vertical-align: baseline;
  aspect-ratio: 0.8;
}
 
.icon {
  height: 1lh;
  position: absolute;
  left: 0;
  bottom: 0;
}

В результате иконка встаёт на низ line-box и занимает всю высоту строки. Выглядит так:

И без линий:

Пример на Codepen

Пример на Codepen

Нюанс этого подхода заключается в том, что иконки рисуются под конкретную гарнитуру. Если семейство шрифтов когда-нибудь изменится, и его метрики будут сильно отличаться от старого, иконки придётся перерисовывать.  

У нас в Авито иконки нарисованы специально под нашу типографику и поставляются в отдельной библиотеке дизайн системы, что позволяет нам оперативно вносить изменения, при необходимости. Они уже пережили одну смену шрифтов, когда мы переезжали с Manrope на собственный Avito Sans.

Поддержка и фолбэки

У юнитов cap и lh статус newly available: актуальные браузеры их уже поддерживают, старые версии — ещё нет. Если старые браузеры вам не нужны — берите и пользуйтесь. А если нужны — у обоих юнитов есть фолбэки.

Значение 1cap можно посчитать самим. Для этого нужно лишь знать метрики шрифта. Заходим на уже знакомый fontdrop.info, загружаем шрифт и ищем там две метрики: sCapHeight и unitsPerEm.

1cap = sCapHeight / unitsPerEm

Для Avito Sans выглядеть это будет так:

1cap = 1440 / 2000 = 0.72em

Осталось подставить это число вместо 1cap --optical-center: calc(0.72em / 2) — и формула первого способа работает в любом браузере.

.outer-span {
  --optical-center: calc(0.72em / 2);
}
 
@supports (height: 1cap) {
  .outer-span {
    --optical-center: calc(1cap / 2);
  }
}

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

<p class="ds-typography-paragraph">
  <span class="outer-span">
    <svg class="icon" />
  </span>
</p>
.ds-typography-paragraph {
  --line-height: 1.3;
}
 
.icon {
  height: var(--line-height);
}
 
@supports (height: 1lh) {
  .icon {
    height: 1lh;
  }
}

Если вдруг у вас line-height: normal — вот почему стоит это исправить. 

Проблемы line-height: normal
  1. Браузеры и операционные системы берут метрики из разных таблиц шрифта — вспомните OS/2 и hhea, — поэтому одно и то же объявление может дать разную высоту строки;

  2. Если ваш шрифт не загрузился и сработал фолбэк-шрифт, normal пересчитается под его метрики.

Поэтому лучше задавать line-height явно — даже если значение будет просто зеркалить normal. А чтобы узнать, что скрывается за normal, можно воспользоваться формулой:

normal = (sTypoAscender - sTypoDescender + sTypoLineGap) / unitsPerEm

Заключение

Итак, мы разобрали довольно много способов выровнять иконку с текстом. Почти все из них — компромиссы с множеством нюансов и рисков. Когда в следующий раз будете выравнивать иконку с текстом, проверьте себя по этому списку. Он поможет подсветить потенциальные проблемы.

  1. Иконка выровнена по оптической середине строки, а не по математической.

  2. Высота строки с иконкой и без неё одинакова, в том числе в многострочном тексте.

  3. Смещение (если оно есть) посчитано с помощью объективных метрик, а не подобрано эмпирически под конкретный случай.

  4. Смена кегля, начертания или размера иконки не смещает иконку с оптического центра.

  5. При переносе текста иконка остаётся в своей строке.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.