Перейти к содержимому
Плотность
Тема

Основания

Типографика

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

Компонент

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

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

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

готово
Разметка
<div class="inst-stack">
  <h2>Заголовок раздела</h2>
  <p>Интерфейс по умолчанию: подписи, кнопки, ячейки.</p>
  <div class="inst-prose">
    <p>Тело ответа агента набирается <code>.inst-prose</code>: 15/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. Весов 600 и 700 в ките нет
Никакого капса с трекингом да Он кричит громче числа, которое подписывает. text-transform не встречается в ките ни разу

Когда использовать

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

Шкала

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

Токен Значение При корне 16px Для чего
--text-2xs 0.6875rem 11px Бейджи, подписи осей. Пол кита, ниже не опускаемся
--text-xs 0.75rem 12px Метаданные, время, подсказки
--text-sm 0.8125rem 13px База интерфейса: подписи, кнопки, ячейки
--text-md 0.9375rem 15px Проза, тела сообщений
--text-lg 1.0625rem 17px Заголовок блока
--text-xl 1.3125rem 21px Заголовок раздела
--text-2xl 1.6875rem 27px Только числа-герои
Пример
2xs · 11px · бейдж, подпись оси xs · 12px · время, метаданные sm · 13px · база интерфейса md · 15px · проза lg · 17px · заголовок блока xl · 21px · заголовок раздела 2xl · 27px · число-герой
Разметка
<div class="inst-stack inst-stack--tight">
  <span style="font-size:var(--text-2xs)">2xs · 11px · бейдж, подпись оси</span>
  <span style="font-size:var(--text-xs)">xs · 12px · время, метаданные</span>
  <span style="font-size:var(--text-sm)">sm · 13px · база интерфейса</span>
  <span style="font-size:var(--text-md)">md · 15px · проза</span>
  <span style="font-size:var(--text-lg)">lg · 17px · заголовок блока</span>
  <span style="font-size:var(--text-xl)">xl · 21px · заголовок раздела</span>
  <span style="font-size:var(--text-2xl)">2xl · 27px · число-герой</span>
</div>

Шаг между соседними ступенями внизу шкалы — один-два пикселя, и это не недоработка: 11, 12 и 13 различают роль, а не важность. Заметный скачок начинается там, где начинается иерархия.

Кегль — единственный ярус в 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 запрещены: только обычное предложение.

Варианты

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

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

Доступность

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

API

ИмяЗначениеЧто делает
класс
inst-proseАбзацный режим: 15/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, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif
--font-monoui-monospace, "Cascadia Code", "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", monospace
--text-2xs/xs/sm/md/lg/xl/2xl
--weight-normal400
--weight-medium500
--leading-tight/ui/prose
--tracking-tight-0.01em
--tracking-normal0

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