Основания
Ритм и форма
Шаг 4px, шкала нарочно разрежена сверху — чтобы «чуть побольше» просто не было в наборе. Компонент берёт отступ не из шкалы, а из яруса ролей: именно он даёт плотности одну точку, через которую она перенастраивает всё сразу.
Разметка
<div class="inst-card inst-stack inst-stack--tight">
<div class="inst-card-title">worldgen-01</div>
<div class="inst-card-sub">Генерация биомов, 3 прохода</div>
<div class="inst-cluster">
<button class="inst-btn inst-btn--sm" type="button">Повторить</button>
<span class="inst-badge" data-tone="ok"><span class="inst-dot"></span>готово</span>
</div>
</div>
Контракт
Отступ берётся по назначению: --pad-panel, не --space-5. Роль
перенастраивается плотностью, ступень шкалы — нет.
.my-panel {
padding: var(--pad-panel);
gap: var(--gap-row);
border-radius: var(--radius-lg);
}
| Что | Обязательно | Почему |
|---|---|---|
| Роль, а не ступень | да | --pad-panel меняется вместе с плотностью, --space-5 — нет. Компонент с зашитой ступенью ломается первым |
| Зазор ставит контейнер | да | Отступ у элемента складывается с внутренним отступом родителя по вертикали и не складывается по горизонтали |
| Радиус из словаря | да | Пять ступеней, названных размером контрола. Своё число разъезжается с соседями на стыке |
Хайрлайн через --hairline |
да | На дробном DPR это 0.5px, и линия остаётся линией, а не полосой |
Доступность
| Цели нажатия | Отступ контрола участвует в размере цели наравне с высотой. В compact мелкие контролы уходят ниже 24px по WCAG 2.5.8 — см. плотность |
Геометрия в px |
Отступы и радиусы не растут вместе с кеглем: плотный инструментальный вид иначе расползается. Растёт текст, а высоты работают как минимум |
| Разделение групп | Ритм — тоже носитель смысла: четыре метрики без рамок читаются как одна группа именно зазором |
| Хайрлайн | На 1x-дисплее остаётся 1px: линия, которая должна быть видна, видна на любом экране |
Шкала
Компонент обращается к ней только через ярус ролей. Прямое использование
--space-* допустимо в приложении и в примитивах потока.
| Токен | Значение |
|---|---|
--space-1 |
2px |
--space-2 |
4px |
--space-3 |
6px |
--space-4 |
8px |
--space-5 |
12px |
--space-6 |
16px |
--space-7 |
24px |
--space-8 |
32px |
--space-9 |
48px |
--space-10 |
64px |
Разрежение сверху — не экономия, а ограничение: между 32 и 48 нет ступени, и это правильно. Шаг, которого нет, нельзя выбрать по ошибке.
Отступы по назначению
Ярус ролей. Значения ниже — для плотности по умолчанию; в compact и
comfortable они другие, см. плотность.
| Токен | По умолчанию | Где |
|---|---|---|
--pad-panel |
--space-5 |
Внутренний отступ панели |
--pad-card |
--space-6 |
Внутренний отступ карточки |
--pad-cell-x |
--space-4 |
Горизонтальный отступ ячейки таблицы |
--row-pad-y |
--space-3 |
Вертикальный отступ строки: задача, ячейка, пункт меню |
--gap-row |
--space-3 |
Зазор между строками в стопке |
--gap-inline |
--space-4 |
Зазор между соседями в ряду |
--control-pad-sm |
--space-4 |
Горизонтальный отступ мелкого контрола |
--control-pad-md |
--space-5 |
Он же у среднего |
--control-pad-lg |
--space-6 |
Он же у крупного |
Зазор ставится между соседями. margin-block на
каждом элементе складывается с внутренним отступом контейнера сверху и снизу,
но не по бокам — и подсветка отступает от рамки по вертикали вдвое дальше, чем
по горизонтали. Правильная форма — X + X { margin-block-start }.
Размеры глифов и жёлобов
Тот же ярус ролей: у каждого размера есть имя, поэтому в компонентах нет констант.
| Токен | По умолчанию | Что это |
|---|---|---|
--size-dot |
6px | Точка состояния |
--size-check |
15px | Чекбокс и радио |
--size-switch-w / --size-switch-h |
30 / 18px | Дорожка свитча |
--size-thumb |
14px | Бегунок |
--size-track |
4px | Дорожка слайдера |
--size-meter |
6px | Толщина меры |
--size-spinner |
14px | Кольцо занятости |
--size-icon |
18px | Иконка — см. иконки |
--size-chevron |
10px | Шеврон, мелкая иконка |
--size-marker |
2px | Несущая метка: полоса вкладки, край сноски, край текущего пункта навигации. Тоньше не видно, толще — спорит с текстом |
--size-gutter |
14px | Левый жёлоб строки задачи |
--size-indent |
14px | Шаг вложенности дерева |
--label-col |
104px | Колонка подписей инспектора |
Радиусы
Шесть ступеней, и выбирает их вложенность. Все значения чётные: при плотности 1.5 нечётный радиус растрируется мимо устройственной сетки.
| Токен | Значение | Для чего |
|---|---|---|
--radius-2xs |
2px | Нижняя ступень. Пометки достают её через --radius-mark, а не по имени |
--radius-xs |
4px | Инлайн: бейджи, чипы, клавиша |
--radius-sm |
6px | Контролы внутри карточки или панели |
--radius-md |
8px | Отдельно стоящие контролы: кнопка, поле, поповер |
--radius-lg |
12px | Карточки, панели, модалка |
--radius-full |
999px | Дорожки, бегунки, аватар |
Скругление КОНТРОЛА берётся не отсюда напрямую, а из --radius-control-sm/md/lg:
оно едет за плотностью вместе с высотой, иначе мелкая кнопка читается пилюлей.
См. плотность.
Скругление ПОМЕТКИ берётся из --radius-mark по причине обратного свойства.
Мельче 8px лестница перестаёт предлагать выбор: когда два радиуса на одной
стороне в сумме больше самой стороны, браузер молча ужимает все радиусы бокса,
пока они не влезут. --radius-xs на точке 6px приезжает тройкой — это половина
бокса, то есть круг; на метре 6px — той же тройкой, то есть пилюлей. А там, где
вторая сторона бокса — это ДАННЫЕ (ширина отрезка дорожки, ширина штриха),
форма начинает меняться вместе с данными. Ярус пометок — единственная ступень,
которая переживает любую пометку в любом кегле.
Правило вложенных радиусов
Внутреннему углу нужен радиус меньше внешнего, иначе внутренний элемент выглядит выпирающим из контейнера. Оптически внутренний радиус равен внешнему минус толщина промежутка, и шкала уже подобрана так, чтобы соседняя ступень вниз давала верный результат при отступе из яруса ролей.
<!-- панель lg → контрол внутри sm; отдельно стоящий контрол — md -->
<div class="inst-panel">
<div class="inst-panel-body">
<div class="inst-card"></div>
</div>
</div>
Скругление на односторонней рамке — всегда баг. border-left вместе с
border-radius даёт обрубок дуги: сноска и край текущего пункта навигации
несут прямой угол намеренно.
Хайрлайн
Браузер никогда не рисует рамку тоньше одного физического пикселя. На
1x-дисплее 0.5px и 1px дают одно и то же, а отрицательное поле в
-0.5px при нарисованной рамке в 1px оставляет полпикселя двойной линии.
Поэтому хайрлайн включается только там, где ему есть куда поместиться.
| Токен | Значение | Когда |
|---|---|---|
--hairline |
1px |
Плотность пикселей ниже 1.5dppx |
--hairline |
0.5px |
@media (min-resolution: 1.5dppx) |
--stroke |
1px |
Линия, которая обязана остаться видимой всегда |
Шов группы кнопок сделан снятой рамкой у соседа — по той же причине.
Правила
| Токен | Значение | Что это |
|---|---|---|
--aside-w |
232px | Боковая колонка оболочки |
--container-max |
76rem | Потолок ширины рабочей области. В rem, потому что мера читаемости, а не геометрия |
--split-side |
18rem | Желаемая ширина боковой части сплита |
--split-main |
30rem | Ниже этого основная часть переносится вниз |
--col-min |
260px | Минимальная колонка адаптивной сетки |
Что брать, а что нет
| Используйте | Возьмите другое |
|---|---|
Роль: --pad-panel, --gap-row, --row-pad-y |
--space-* в компоненте — плотность перенастраивает ярус ролей, а не шкалу. Список ролей целиком — токены |
Зазор между соседями — примитив потока .inst-stack / .inst-cluster / .inst-grid |
Утилита отступа (mt-3, p-2) — их в библиотеке нет и не будет, см. поток |
Шаг зазора именем: обычный, --tight, --loose |
Свой зазор числом — «чуть побольше» не должно быть доступным решением. Три шага описаны в потоке |
Радиус по вложенности: контейнер lg, контрол внутри sm |
Один радиус на всё — внутренний элемент с радиусом контейнера выпирает. Пример — карточка |
--hairline для линии, которая не должна кричать |
1px числом — на 2x-дисплее это вдвое толще нужного, см. хайрлайн |
API
| Имя | Что делает |
|---|---|
| класс | |
inst-stack | Вертикальный поток, зазор --gap-inline |
inst-cluster | Горизонтальный поток с переносом |
inst-cluster-spacer | Разрыв в ряду: всё после него уходит вправо |
inst-grid | Адаптивная сетка auto-fit по --col-min |
| модификатор | |
inst-stack--tight | Шаг зазора |
inst-stack--loose | Шаг зазора |
inst-cluster--tight | Шаг зазора |
inst-cluster--loose | Шаг зазора |
inst-grid--tight | Другая минимальная колонка |
inst-grid--wide | Другая минимальная колонка |
| Имя | Значение | Откуда |
|---|---|---|
| токен | ||
--space-1Шаг шкалы | 2px | одно на все ячейки |
--space-2Шаг шкалы | 4px | одно на все ячейки |
--space-3Шаг шкалы | 6px | одно на все ячейки |
--space-4Шаг шкалы | 8px | одно на все ячейки |
--space-5Шаг шкалы | 12px | одно на все ячейки |
--space-6Шаг шкалы | 16px | одно на все ячейки |
--space-7Шаг шкалы | 24px | одно на все ячейки |
--space-8Шаг шкалы | 32px | одно на все ячейки |
--space-9Шаг шкалы | 48px | одно на все ячейки |
--space-10Шаг шкалы | 64px | одно на все ячейки |
--pad-panelВнутренний отступ панели | var(--space-5) | база +14: плотность · масштаб |
--pad-cardВнутренний отступ карточки | var(--space-6) | база +14: плотность · масштаб |
--pad-cell-xГоризонтальный отступ ячейки | var(--space-4) | база +14: плотность · масштаб |
--row-pad-yВертикальный отступ строки | var(--space-3) | база +14: плотность · масштаб |
--gap-rowЗазор между строками | var(--space-3) | база +14: плотность · масштаб |
--gap-inlineЗазор в ряду | var(--space-4) | база +14: плотность · масштаб |
--control-pad-smОтступ малого контрола | calc(var(--control-h-sm) * var(--control-ratio-sm)) | одно на все ячейки |
--control-pad-mdОтступ базового контрола | calc(var(--control-h-md) * var(--control-ratio-md)) | одно на все ячейки |
--control-pad-lgОтступ крупного контрола | calc(var(--control-h-lg) * var(--control-ratio-lg)) | одно на все ячейки |
--radius-2xsНижняя ступень лестницы. Пометки берут её через --radius-mark | 2px | база +4: масштаб |
--radius-markСкругление пометки: всё, что кит рисует мельче 8px — точка, штрих, метр и дорожка | var(--radius-2xs) | одно на все ячейки |
--radius-xsИнлайн: бейджи, чипы, клавиша | 4px | база +4: масштаб |
--radius-control-smСкругление мелкого контрола. Едет за плотностью вместе с высотой | var(--radius-sm) | база +14: плотность · масштаб |
--radius-control-mdСкругление контрола | var(--radius-md) | база +14: плотность · масштаб |
--radius-control-lgСкругление крупного контрола | var(--radius-md) | база +14: плотность · масштаб |
--radius-smКонтролы внутри карточки | 6px | база +4: масштаб |
--radius-mdОтдельно стоящие контролы | 8px | база +4: масштаб |
--radius-lgКарточки и панели | 12px | база +4: масштаб |
--radius-fullКруг и капсула | 999px | одно на все ячейки |
--hairlineВолосок. На 1x равен пикселю, выше — половине | 1px | база +1: экран |
--strokeЛиния, которая обязана быть видна всегда | 1px | одно на все ячейки |
--aside-wБоковая колонка оболочки | 232px | база +4: масштаб |
--container-maxПотолок ширины рабочей области | 76rem | база +4: масштаб |
--split-sideЖелаемая ширина боковой части сплита | 18rem | база +4: масштаб |
--split-mainПорог переноса основной части | 30rem | база +4: масштаб |
--col-minМинимальная колонка адаптивной сетки | 260px | база +4: масштаб |
| Имя | Что делает |
|---|---|
| класс | |
inst-stack | Вертикальный поток, зазор --gap-inline |
inst-cluster | Горизонтальный поток с переносом |
inst-cluster-spacer | Разрыв в ряду: всё после него уходит вправо |
inst-grid | Адаптивная сетка auto-fit по --col-min |
| модификатор | |
inst-stack--tight | Шаг зазора |
inst-stack--loose | Шаг зазора |
inst-cluster--tight | Шаг зазора |
inst-cluster--loose | Шаг зазора |
inst-grid--tight | Другая минимальная колонка |
inst-grid--wide | Другая минимальная колонка |
| Имя | Значение | Откуда |
|---|---|---|
| токен | ||
--space-1Шаг шкалы | 2px | одно на все ячейки |
--space-2Шаг шкалы | 4px | одно на все ячейки |
--space-3Шаг шкалы | 6px | одно на все ячейки |
--space-4Шаг шкалы | 8px | одно на все ячейки |
--space-5Шаг шкалы | 12px | одно на все ячейки |
--space-6Шаг шкалы | 16px | одно на все ячейки |
--space-7Шаг шкалы | 24px | одно на все ячейки |
--space-8Шаг шкалы | 32px | одно на все ячейки |
--space-9Шаг шкалы | 48px | одно на все ячейки |
--space-10Шаг шкалы | 64px | одно на все ячейки |
--pad-panelВнутренний отступ панели | var(--space-5) | база +14: плотность · масштаб |
--pad-cardВнутренний отступ карточки | var(--space-6) | база +14: плотность · масштаб |
--pad-cell-xГоризонтальный отступ ячейки | var(--space-4) | база +14: плотность · масштаб |
--row-pad-yВертикальный отступ строки | var(--space-3) | база +14: плотность · масштаб |
--gap-rowЗазор между строками | var(--space-3) | база +14: плотность · масштаб |
--gap-inlineЗазор в ряду | var(--space-4) | база +14: плотность · масштаб |
--control-pad-smОтступ малого контрола | calc(var(--control-h-sm) * var(--control-ratio-sm)) | одно на все ячейки |
--control-pad-mdОтступ базового контрола | calc(var(--control-h-md) * var(--control-ratio-md)) | одно на все ячейки |
--control-pad-lgОтступ крупного контрола | calc(var(--control-h-lg) * var(--control-ratio-lg)) | одно на все ячейки |
--radius-2xsНижняя ступень лестницы. Пометки берут её через --radius-mark | 2px | база +4: масштаб |
--radius-markСкругление пометки: всё, что кит рисует мельче 8px — точка, штрих, метр и дорожка | var(--radius-2xs) | одно на все ячейки |
--radius-xsИнлайн: бейджи, чипы, клавиша | 4px | база +4: масштаб |
--radius-control-smСкругление мелкого контрола. Едет за плотностью вместе с высотой | var(--radius-sm) | база +14: плотность · масштаб |
--radius-control-mdСкругление контрола | var(--radius-md) | база +14: плотность · масштаб |
--radius-control-lgСкругление крупного контрола | var(--radius-md) | база +14: плотность · масштаб |
--radius-smКонтролы внутри карточки | 6px | база +4: масштаб |
--radius-mdОтдельно стоящие контролы | 8px | база +4: масштаб |
--radius-lgКарточки и панели | 12px | база +4: масштаб |
--radius-fullКруг и капсула | 999px | одно на все ячейки |
--hairlineВолосок. На 1x равен пикселю, выше — половине | 1px | база +1: экран |
--strokeЛиния, которая обязана быть видна всегда | 1px | одно на все ячейки |
--aside-wБоковая колонка оболочки | 232px | база +4: масштаб |
--container-maxПотолок ширины рабочей области | 76rem | база +4: масштаб |
--split-sideЖелаемая ширина боковой части сплита | 18rem | база +4: масштаб |
--split-mainПорог переноса основной части | 30rem | база +4: масштаб |
--col-minМинимальная колонка адаптивной сетки | 260px | база +4: масштаб |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument