Раскладка
Примитивы потока
Три способа расставить элементы: стопкой, рядом, сеткой. У каждого три шага зазора, названные намерением, а не числом.
Разметка
<div class="inst-stack">
<div class="inst-cluster">
<button class="inst-btn inst-btn--sm" type="button">Фильтры</button>
<button class="inst-btn inst-btn--sm" type="button">Период</button>
<span class="inst-cluster-spacer"></span>
<button class="inst-btn inst-btn--sm inst-btn--primary" type="button">Запустить</button>
</div>
<div class="inst-grid inst-grid--tight">
<div class="inst-metric"><div class="inst-metric-label">В работе</div><div class="inst-metric-value">7</div></div>
<div class="inst-metric"><div class="inst-metric-label">В очереди</div><div class="inst-metric-value">5</div></div>
<div class="inst-metric"><div class="inst-metric-label">Упало</div><div class="inst-metric-value">1</div></div>
</div>
</div>
Использование
<div class="inst-stack">
<div>…</div>
<div>…</div>
</div>
| Что | Обязательно | Почему |
|---|---|---|
| Зазор ставит контейнер, а не элемент | да | Отступ у элемента складывается с внутренним отступом контейнера сверху и снизу, но не по бокам: блок отъезжает от рамки по вертикали вдвое дальше, чем по горизонтали |
| Шаг зазора — модификатором | да | --tight и --loose названы намерением. Число в разметке ломается на первой же смене плотности |
Один inst-cluster-spacer на ряд |
да | Второй ничего не даст: первый уже забрал остаток |
<ul> вместо <div>, если это список |
нет, но обычно да | Примитивы семантики не несут |
Когда использовать
| Используйте | Возьмите другое |
|---|---|
Вертикальный ритм между блоками — inst-stack |
Ограничение ширины и боковые поля — контейнер |
Горизонтальный ряд, который переносится, — inst-cluster |
Две колонки разной важности — сплит: у него есть порог переноса |
Карточки в адаптивную сетку — inst-grid |
Табличные данные — таблица: у сетки нет строк и заголовков колонок |
| Полоса действий у заголовка | Действия экрана — inst-page-actions, шапка экрана; действия секции — секция |
Варианты
Стопка
Разметка
<div class="inst-stack inst-stack--tight">
<div class="inst-card"><div class="inst-card-title">Первый</div></div>
<div class="inst-card"><div class="inst-card-title">Второй</div></div>
</div>
| Класс | Зазор | Когда |
|---|---|---|
inst-stack |
--pad-panel |
Умолчание: блоки экрана, секции, панели |
inst-stack--tight |
--gap-row |
Строки внутри блока: пары ключ-значение, список задач |
inst-stack--loose |
--space-7 |
Крупные смысловые разделы длинного экрана |
Кнопка, бейдж, тег и сегментированный контрол в колонке не растягиваются: кнопка во всю ширину карточки — тот же признак самодельного кита, что и иконочная кнопка прямоугольником. Поля и карточки ширину колонки, наоборот, занимают. Список закрытый и совпадает с инлайновыми компонентами кита.
Это работает во всех колонках кита — стопке, форме, филдсете, поле,
секции. В своей колонке приложения — нет: display: flex; flex-direction: column, написанный руками, растянет их заново. Это довод взять примитив, а
не писать колонку.
Кластер
Разметка
<div class="inst-cluster inst-cluster--loose">
<span class="inst-badge" data-tone="ok"><span class="inst-dot"></span>готово</span>
<span class="inst-badge" data-tone="running"><span class="inst-dot"></span>идёт</span>
<span class="inst-cluster-spacer"></span>
<button class="inst-btn inst-btn--sm" type="button">Ещё</button>
</div>
| Класс | Зазор | Когда |
|---|---|---|
inst-cluster |
--gap-inline |
Умолчание: кнопки, бейджи, контролы в ряд |
inst-cluster--tight |
--space-2 |
Элементы, читающиеся как одна группа: теги, чипы |
inst-cluster--loose |
--pad-panel |
Разные по смыслу группы в одной строке |
Кластер переносится всегда (flex-wrap: wrap) и выравнивает детей по
центру поперечной оси. inst-cluster-spacer — пустой элемент с
margin-inline-start: auto: всё после него уезжает к дальнему краю.
Сетка
Разметка
<div class="inst-grid inst-grid--wide">
<div class="inst-card"><div class="inst-card-title">Карточка</div>
<div class="inst-card-sub">Колонки перестраиваются сами: auto-fit, без единого запроса.</div></div>
<div class="inst-card"><div class="inst-card-title">Карточка</div>
<div class="inst-card-sub">min() не даёт колонке вылезти за контейнер на узком экране.</div></div>
</div>
| Класс | Минимальная колонка | Когда |
|---|---|---|
inst-grid |
--col-min, 260px |
Умолчание: карточки, панели |
inst-grid--tight |
180px | Мелкие ячейки: метрики, плитки состояний |
inst-grid--wide |
380px | Крупные блоки с текстом внутри |
Число колонок не задаётся: repeat(auto-fit, minmax(min(var(--col-min), 100%), 1fr))
считает его сам. min(…, 100%) обязателен — без него колонка шириной 380px не
помещается в контейнер шириной 320px и вылезает наружу вместе с горизонтальной
прокруткой.
Это первый уровень адаптивности, интринсик: работает всегда, в том числе там, где контейнера-предка нет.
/* Своя плотность сетки — одна строка */
.my-board { --col-min: 320px; }
Правила
Почему здесь нет mt-3 и p-2
Утилит отступов в ките нет и не будет, и это не эстетика.
Шкала кита нарочно разрежена сверху: между соседними шагами разрыв растёт, чтобы «чуть побольше» не было доступным решением. Набор из двухсот утилит возвращает его первым классом — и вместе с ним переносит решение о вертикальном ритме из кита в разметку каждого экрана. Через полгода одинаковые на вид экраны отличаются на два пикселя в тридцати местах, и починить это можно только поштучно.
Вместо утилит — три примитива с тремя шагами зазора, названными намерением.
Плотность перенастраивает все три разом, потому что зазор приходит из ролей
(--pad-panel, --gap-row, --gap-inline), а не из числа в разметке. Полное
рассуждение — в конституции.
Так Зазор ставит контейнер
Нужен воздух между блоками — положите их в стопку. Шаг выбирается модификатором.
Не так Отступ у элемента
Он складывается с внутренним отступом контейнера по вертикали и не складывается по горизонтали. Блок отъезжает от рамки неравномерно.
Так Три шага, названные намерением
--tight, умолчание, --loose. Плотность перенастраивает все три разом.
Не так «Чуть побольше» числом
Через полгода одинаковые на вид экраны расходятся на два пикселя в тридцати местах, и чинится это только поштучно.
Доступность
| Порядок | Все три примитива сохраняют порядок разметки: визуальный и клавиатурный совпадают. order и row-reverse в ките не применяются |
| Перенос | Кластер и сетка переносятся сами — при увеличении кегля до 200% содержимое не обрезается и не даёт горизонтальной прокрутки |
| Распорка | inst-cluster-spacer пуст и в дереве доступности отсутствует: он не сообщает ничего сверх порядка |
| Плотность | Все три зазора приходят из ролей и перенастраиваются data-density разом. Компонент, у которого зашито число, ломается здесь первым |
| Семантика | Примитивы — <div>. Если группа элементов — список, ставьте <ul> и класс на него |
API
| Имя | Значение | Что делает |
|---|---|---|
| класс | ||
inst-stack | — | Колонка. Зазор --pad-panel |
inst-cluster | — | Ряд с переносом, выравнивание по центру. Зазор --gap-inline |
inst-cluster-spacer | — | Прижать хвост ряда к дальнему краю |
inst-grid | — | Адаптивная сетка. Зазор --pad-panel |
| модификатор | ||
inst-stack--tight | — | Шаг зазора |
inst-stack--loose | — | Шаг зазора |
inst-cluster--tight | — | Шаг зазора |
inst-cluster--loose | — | Шаг зазора |
inst-grid--tight | — | Минимальная ширина колонки |
inst-grid--wide | — | Минимальная ширина колонки |
| переменная | ||
--col-min | 260px | |
| токен | ||
--pad-panel | var(--space-5) | |
--gap-row | var(--space-3) | |
--gap-inline | var(--space-4) | |
--space-2 | 4px | |
--space-7 | 24px | |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument