Основания
Типографика
Семь ступеней и два начертания. База документа — интерфейс, а не проза: 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 | Только числа-герои |
Разметка
<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 | Заголовки, подписи кнопок, шапка таблицы, бейдж |
Разметка
<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> оформлен в базе и класса не требует.
Разметка
<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 знаками: длинная строка теряет начало следующей |
| Заголовки | Уровни h1–h6 — настоящие. Кегль не заменяет структуру: скринридер читает уровень, а не размер |
| Обрезка | .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-sans | ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif | |
--font-mono | ui-monospace, "Cascadia Code", "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", monospace | |
--text-2xs/xs/sm/md/lg/xl/2xl | — | |
--weight-normal | 400 | |
--weight-medium | 500 | |
--leading-tight/ui/prose | — | |
--tracking-tight | -0.01em | |
--tracking-normal | 0 | |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument