Перейти к содержимому
EN
Масштаб
Плотность
Тема
Акцент

Основания

Типографика

Семь ступеней и два начертания. База документа — интерфейс, а не проза: 14/1.4, потому что подписи, ячейки, кнопки и строки задач — частый случай.

Компонент
2xl · 28px · число-герой xl · 22px · заголовок раздела lg · 18px · заголовок блока md · 16px · проза, имя панели sm · 14px · база интерфейса xs · 12.5px · время, метаданные 2xs · 11px · бейдж, подпись оси
Разметка
<div class="inst-stack inst-stack--tight">
  <span style="font-size:var(--text-2xl)">2xl · 28px · число-герой</span>
  <span style="font-size:var(--text-xl)">xl · 22px · заголовок раздела</span>
  <span style="font-size:var(--text-lg)">lg · 18px · заголовок блока</span>
  <span style="font-size:var(--text-md)">md · 16px · проза, имя панели</span>
  <span style="font-size:var(--text-sm)">sm · 14px · база интерфейса</span>
  <span style="font-size:var(--text-xs)">xs · 12.5px · время, метаданные</span>
  <span style="font-size:var(--text-2xs)">2xs · 11px · бейдж, подпись оси</span>
</div>

Ступень существует, только если её видно. Отношения соседних кеглей — 1.222 · 1.273 в дисплейной части и 1.136 · 1.12 · 1.143 · 1.125 в рабочей: ниже этого глаз перестаёт различать размеры, и три ступени начинают работать как одна.

Так шкала выглядит в деле — интерфейс, проза и метка на одном экране:

Пример

Заголовок раздела

Интерфейс по умолчанию: подписи, кнопки, ячейки.

Тело ответа агента набирается .inst-prose: 16/1.65, пропорциональные цифры и строка, ограниченная 68 знаками.

готово
Разметка
<div class="inst-stack">
  <h2>Заголовок раздела</h2>
  <p>Интерфейс по умолчанию: подписи, кнопки, ячейки.</p>
  <div class="inst-prose">
    <p>Тело ответа агента набирается <code>.inst-prose</code>: 16/1.65,
    пропорциональные цифры и строка, ограниченная 68 знаками.</p>
  </div>
  <span class="inst-badge" data-tone="ok"><span class="inst-dot"></span>готово</span>
</div>

Контракт

Кегль берётся из шкалы, начертание — из трёх. Заголовок не имеет права кричать громче данных, которые он подписывает.

.my-label {
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: var(--leading-ui);
}
Что Обязательно Почему
Кегль из --text-* да Семь ступеней. Восьмая, выдуманная на месте, ломает лестницу заголовков
Интерлиньяж по работе да --leading-ui для интерфейса, --leading-prose для текста, который читают абзацами
Три начертания да normal, medium, mono. Вес 700 в библиотеке запрещён
Никакого капса с трекингом да Он кричит громче числа, которое подписывает. text-transform не встречается в библиотеке ни разу

Доступность

Масштабирование Кегли в rem, поэтому настройка «размер шрифта по умолчанию» в браузере работает. Высоты контролов — min-block-size, текст не упирается в потолок
Пол кегля 11px. Ниже в библиотеке размера нет и не заводится
Порог чтения Любой читаемый текст стоит на токене с порогом 4.5:1. --text-faint (3:1) для текста не применяется — см. цвет
Мера строки Проза ограничена 68 знаками: длинная строка теряет начало следующей
Заголовки Уровни h1–h6 — настоящие. Кегль не заменяет структуру: скринридер читает уровень, а не размер
Обрезка .inst-u-truncate прячет текст визуально, но не от скринридера. Полный текст нужен в title — так делает инспектор

Шкала

Семь ступеней, не больше. Каждая лишняя — это решение, которое кто-то примет неправильно.

Токен Значение При корне 16px Отношение Для чего
--text-2xs 0.6875rem 11px — Бейджи, подписи осей. Минимальный кегль, ниже не опускаемся
--text-xs 0.78125rem 12.5px 1.136 Метаданные, время, подсказки
--text-sm 0.875rem 14px 1.12 База интерфейса: подписи, кнопки, ячейки
--text-md 1rem 16px 1.143 Проза, тела сообщений, имя панели
--text-lg 1.125rem 18px 1.125 Заголовок блока
--text-xl 1.375rem 22px 1.222 Заголовок раздела
--text-2xl 1.75rem 28px 1.273 Только числа-герои

Ступень существует, только если её видно. В рабочей части шкалы отношения держатся в полосе 1.12–1.143. Это порог: ниже него глаз перестаёт различать кегли, и соседние ступени начинают работать как одна — иерархию приходится целиком перекладывать на цвет и вес, а их разрядов мало. Дисплейные ступени расходятся шире, 1.222 и 1.273, и тоже намеренно: заголовок отличается от заголовка на бо́льшую долю, чем подпись от подписи.

Шкала считается снизу вверх, от пола в 11px, а не от корня документа. То, что --text-md совпал с 16px, — удобное совпадение, а не привязка.

Начертаний по-прежнему два, и 700 запрещён. Значит, крупную иерархию несёт только размер — тем более он обязан быть различимым.

Кегль — единственный ярус в rem. Интерфейс обязан уважать увеличенный размер шрифта по умолчанию в браузере: в админке сидят весь день, и это первая настройка, которую делает человек с ослабленным зрением. Масштабирование страницы работает и с px, но настройку «размер шрифта по умолчанию» px игнорирует молча.

Всё остальное остаётся в px, потому что это геометрия, а не текст: хайрлайн, точка состояния, жёлоб, радиус. Следствие, которое нельзя игнорировать: если текст растёт, а высоты контролов заданы в px, текст однажды упрётся в потолок — поэтому высоты применяются как min-block-size, а не block-size.

Заголовки

Умолчания элементов, класса не требуют. Все несут --weight-medium, --leading-tight, --tracking-tight и text-wrap: balance.

Элемент Кегль
<h1> --text-2xl
<h2> --text-xl
<h3> --text-lg
<h4> <h5> <h6> --text-md
Пример

Заголовок раздела

Внутри .inst-prose у заголовка свой ритм: воздуха сверху вдвое больше, чем снизу. Заголовок принадлежит тому, что под ним, и разделять текст поровну ему нельзя.

Подзаголовок

Без этого правила заголовок висит посередине между своим абзацем и чужим — и не отделяет ни одного от другого.

Разметка
<div class="inst-prose">
  <h2>Заголовок раздела</h2>
  <p>Внутри <code>.inst-prose</code> у заголовка свой ритм: воздуха сверху
  вдвое больше, чем снизу. Заголовок принадлежит тому, что под ним, и
  разделять текст поровну ему нельзя.</p>
  <h3>Подзаголовок</h3>
  <p>Без этого правила заголовок висит посередине между своим абзацем и
  чужим — и не отделяет ни одного от другого.</p>
</div>

ЗАГОЛОВКИ КАПСОМ и Title Case запрещены: только обычное предложение.

Варианты

Два. 700 в шрифте есть и всё равно запрещён: на макете из хайрлайнов и воздуха он читается как крик.

Медиум — 600, а не 500, и это признание факта. У Segoe UI настоящего 500 нет, и системный стек снапает его к Semibold: замер по пикселям показал, что 500 и 600 совпадают строка в строку. На macOS SF Pro даёт настоящий Medium — то есть рисунок различался на двух платформах, и выбирал это шрифт, а не автор.

Токен Значение Где
--weight-normal 400 Всё по умолчанию
--weight-medium 600 Заголовки, подписи кнопок, шапка таблицы, бейдж
Пример
400 — обычный текст, ячейки, подписи полей 600 — заголовок, кнопка, шапка таблицы 700 — в библиотеке запрещено: на плоском макете читается как крик
Разметка
<div class="inst-stack inst-stack--tight">
  <span>400 — обычный текст, ячейки, подписи полей</span>
  <span style="font-weight:var(--weight-medium)">600 — заголовок, кнопка, шапка таблицы</span>
  <span style="font-weight:700">700 — в библиотеке запрещено: на плоском макете читается как крик</span>
</div>

Интерлиньяж

Токен Значение Для чего
--leading-tight 1.25 Заголовки, однострочный интерфейс
--leading-ui 1.4 База документа: переносящиеся подписи, ячейки таблиц
--leading-prose 1.65 Проза

Трекинг

Оптическая коррекция, не стиль: крупный кегль по умолчанию выглядит разреженным.

Токен Значение Где
--tracking-tight -0.01em Заголовки
--tracking-normal 0 Всё остальное

Мелкий кегль не разрежается никогда: разрядка на 11px — это не акцент, а потеря слова.

Семейства

Токен Стек
--font-sans ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial
--font-mono ui-monospace, Cascadia Code, JetBrains Mono, SF Mono, Consolas, Liberation Mono

Шрифт системный: веб-шрифт добавил бы сетевой запрос и вспышку подмены ради эффекта, который в плоском инструментальном интерфейсе не читается.

Моноширинный приходит вместе с оптической поправкой 0.925em — моно воспринимается крупнее санса на том же кегле. Поправка объявлена один раз на code, kbd, samp, pre и .inst-u-mono: когда у утилиты было своё правило, <code class="inst-u-mono"> получал 0.925 × 0.925 и уезжал под пол в 11px. Внутри блока кода поправка уже применена контейнером и второй раз не накладывается.

Табличные цифры

font-variant-numeric: tabular-nums стоит на body, то есть по умолчанию на всём. В инструментальном интерфейсе почти любое число либо стоит в колонке, либо обновляется на месте, и пропорциональные цифры дёргают оба случая.

Что Как отключить
Абзац прозы .inst-prose — уже отключает
Отдельный кусок текста .inst-u-prose-nums

Обратного переключателя нет: табличные и так везде.

Композиции

Опт-ин, а не умолчание. Базой должен быть частый случай, иначе каждый компонент вынужден переобъявлять размер, а любой div приложения без класса наследует не тот кегль.

<div class="inst-prose">
  <p>Тело ответа агента.</p>
  <p>Второй абзац отступает от первого сам.</p>
</div>
Что делает .inst-prose Значение
Кегль и интерлиньяж --text-md / --leading-prose
Цифры Пропорциональные
Ширина строки 68ch — мера читаемости
Перенос text-wrap: pretty у абзацев
Ритм Зазор ставится между соседями: > * + * получает --space-5
Списки Отступ --space-7, между пунктами --space-2

Отдельного компонента для стриминга нет и не нужно: тело — это .inst-prose, а признак того, что текст ещё поступает, — .inst-caret.

Клавиши

Инструмент живёт на клавиатуре, поэтому клавиша — элемент интерфейса, а не безымянный моноширинный кусок. <kbd> оформлен в базе и класса не требует.

Пример
Ctrl K — поиск по прогонам Esc — остановить прогон
Разметка
<span><kbd>Ctrl</kbd> <kbd>K</kbd> — поиск по прогонам</span>
<span><kbd>Esc</kbd> — остановить прогон</span>

Правила

Что брать, а что нет

Используйте Возьмите другое
--text-sm — умолчание интерфейса. Ничего не пишете, всё уже так Абзацы, которые читают подряд — класс .inst-prose, он же тело ответа агента
--text-2xl — число-герой, одно на блок Заголовок экрана — --text-xl через <h2>. Крупный кегль у подписи кричит громче числа, которое он подписывает
--text-2xs — бейдж, подпись оси Метаданные и время — --text-xs: 11px это пол, а не рабочий кегль. Пример — строка лога
--weight-medium для акцента 700 — его в библиотеке нет. Разделяйте кеглем и цветом, как в метрике
Моноширинный для идентификаторов и путей Моноширинный для прозы — обычный текст остаётся сансом, см. блок кода

API

Классы
ИмяЧто делает
класс
inst-proseАбзацный режим: 16/1.65, пропорциональные цифры, 68ch
inst-u-monoМоноширинный кусок в потоке
inst-u-prose-numsПропорциональные цифры точечно
inst-u-dimПриглушение до --text-secondary
inst-u-truncateОбрезка одной строки многоточием
Токены
ИмяЗначениеОткуда
токен
--font-sansui-sans-serif, system-ui, -apple-system, " , Roboto, " , Arial, sans-serifодно на все ячейки
--font-monoui-monospace, " , " , " , Consolas, " , monospaceодно на все ячейки
--text-2xs/xs/sm/md/lg/xl/2xl—одно на все ячейки
--weight-normal400одно на все ячейки
--weight-medium600одно на все ячейки
--leading-tight/ui/prose—одно на все ячейки
--tracking-tight-0.01emодно на все ячейки
--tracking-normal0одно на все ячейки

Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument