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

Основания

Токены

Полный справочник. Четыре яруса, и разделение между ними — весь смысл файла: компонент имеет право видеть ярусы 2 и 3, ярус 1 — никогда.

Компонент
Ярусы
1. Рампы
Сырые шаги. Не меняются между темами
2. Семантика
Для чего цвет нужен. Одно объявление через light-dark()
3. Роли
Отступы и размеры по назначению. Их перенастраивает плотность
4. Компонент
--btn-bg и подобные, 2–3 переменные
Разметка
<div class="inst-panel">
  <div class="inst-panel-header">
    <span class="inst-panel-title">Ярусы</span>
  </div>
  <div class="inst-panel-body">
    <dl class="inst-kv">
      <dt>1. Рампы</dt><dd>Сырые шаги. Не меняются между темами</dd>
      <dt>2. Семантика</dt><dd>Для чего цвет нужен. Одно объявление через light-dark()</dd>
      <dt>3. Роли</dt><dd>Отступы и размеры по назначению. Их перенастраивает плотность</dd>
      <dt>4. Компонент</dt><dd>--btn-bg и подобные, 2–3 переменные</dd>
    </dl>
  </div>
</div>

Использование

Компонент видит ярусы 2 и 3 — семантику и роли. Ярус 1 не видит никогда.

.my-thing {
  /* ярус 2: для чего цвет нужен */
  color: var(--text-primary);
  /* ярус 3: отступ по назначению */
  padding: var(--pad-panel);
  /* ярус 4: точка настройки самого компонента.
     У кнопки таких пять: --btn-bg и соседи */
  background: var(--btn-bg);
}
Что Обязательно Почему
Ярусы 2 и 3 в компоненте да Ярус 1 — сырьё: рампа между темами не меняется, и обращение к ней захардкоживает светлую тему
Один экземпляр каждого параметра да Дубликат — это будущее расхождение, и оно наступает быстрее, чем кажется
2–3 переменные на компонент да Ярус 4 — точки настройки, а не копия всех свойств
Значение через light-dark() да Второй темы как отдельного блока не существует

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

Используйте Возьмите другое
Ярус 2 — семантика: --text-primary, --surface-raised, --ok-text Ярус 1 (--n-3, --a-4) — это захардкоженная светлая тема. Разбор рамп — в цвете
Ярус 3 — роли: --pad-panel, --control-h-md, --size-dot Число в компоненте — оно сломает плотность, см. плотность
Ярус 4 — 2–3 переменные компонента для варианта Копию всего блока правил — вариант должен быть двумя строками, как у кнопки
Новый параметр — как роль Новую константу — константа переживёт вас и сломает compact. Шкалы — в ритме и форме
Переопределение роли в приложении !important — роли объявлены через :where(), борьбы не будет. Пример — форма

Устройство

Сырые шаги. Между темами не меняются. Рампа непрерывна по определению: шаг, не занятый семантикой, — резерв под приложение и графики, а не мусор.

Группа Токены Тон
Нейтраль --n-0--n-14 --hue-neutral (75 тёплый / 250 холодный)
Акцент --a-1--a-6 250°
Успех --ok-1 --ok-2 --ok-3 --ok-4 --ok-5 --ok-6 150°
Предупреждение --warn-1 --warn-3 --warn-4 --warn-5 --warn-6 85°
Ошибка --err-1 --err-3 --err-4 --err-5 --err-6 25°

Шага 2 у статусов нет: у них закреплённые роли (1 — фон в светлой, 3 — текст в тёмной, 4 — заливка в светлой, 5 — текст в светлой, 6 — резерв под графики), и свободный шаг между 1 и 3 не понадобился ни разу. Значения светлот и разбор ролей — в цвете.

Ручка тона

Токен Значение Что делает
--hue-neutral 75 Уклон нейтрали. 75 — тёплый, 250 — холодный. Цветность 0.002–0.006: ниже порога осознанного замечания

Ярус 2 — семантика

Одно объявление на токен через light-dark(). Второго блока токенов не существует, поэтому и расходиться нечему.

Поверхности

Токен Светлая Тёмная
--surface-sunken --n-2 --n-14
--surface-page --n-1 --n-13
--surface-raised --n-0 --n-12
--surface-overlay --n-0 --n-11
--surface-field --n-1 --n-14
--surface-hover чёрный 3.5% белый 4.5%
--surface-active чёрный 6.5% белый 8%
--surface-selected --a-4 14%, в обеих темах

Текст

Токен Светлая Тёмная Порог
--text-primary --n-12 --n-1 4.5:1
--text-secondary --n-9 --n-5 4.5:1
--text-muted --n-8 --n-6 4.5:1
--text-faint --n-7 --n-7 3:1 — не для текста, который читают

Рамки

Токен Светлая Тёмная Требование
--border-subtle чёрный 7% белый 6% Декоративная
--border чёрный 12% белый 11% Декоративная
--border-strong чёрный 22% белый 20% Декоративная
--border-control чёрный 46% белый 38% Несущая, 3:1

Акцент

Токен Светлая Тёмная Работа
--accent-text --a-5 --a-3 Текст, 4.5:1
--accent-mark --a-4 --a-3 Метка без подписи, 3:1 против поверхности и дорожки
--accent-bg --a-1 --a-4 15% Тонированный фон
--accent-solid --a-4, в обеих темах Заливка кнопки, потолок светлоты 0.56
--accent-on --n-0, в обеих темах Подпись на заливке
--accent-hover --a-5, в обеих темах Уходит от цвета подписи, а не к нему
--accent-border --a-4 --a-3 Рамка акцентированного контрола
--focus-ring --a-4 --a-3 Кольцо фокуса

Статусы

Токен Светлая Тёмная
--ok-text --ok-5 --ok-3
--ok-bg --ok-1 --ok-4 16%
--warn-text --warn-5 --warn-3
--warn-bg --warn-1 --warn-4 16%
--err-text --err-5 --err-3
--err-bg --err-1 --err-4 16%

Третьего токена — «заливки» между текстом и фоном — нет. Исключение одно, акцент: у него есть случай, которого нет у статусов, — заливка под белой подписью.

Тон через атрибут

data-tone ставит три переменные, компонент читает их и больше ничего о тоне не знает.

Значение --tone-ink --tone-mark --tone-bg
neutral --text-secondary --text-secondary --surface-sunken
running --accent-text --accent-mark --accent-bg
ok --ok-text --ok-text --ok-bg
warn --warn-text --warn-text --warn-bg
error --err-text --err-text --err-bg

Графики

Токен Тон Светлая Тёмная
--chart-1 280° L 0.520 L 0.700
--chart-2 320° L 0.600 L 0.760
--chart-3 355° L 0.550 L 0.720
--chart-4 55° L 0.650 L 0.800
--chart-5 115° L 0.580 L 0.740
--chart-6 190° L 0.620 L 0.780

Прочее

Токен Светлая Тёмная
--track чёрный 10% белый 16%
--scrim чёрный 32% чёрный 58%
--shadow-color-near чёрный 6% чёрный 30%
--shadow-color-far чёрный 10% чёрный 45%
--shadow-popover двухслойная, короткая
--shadow-modal двухслойная, длинная

Порядок слоёв

Токен Значение
--z-sticky 10
--z-popover 100
--z-modal 1000

Ярус 3 — роли

Единственный ярус, который перенастраивает плотность, и единственная причина, по которой в компонентах нет констант. Объявлен через :where() — нулевая специфичность, приложение переопределяет без борьбы. Значения ниже — для плотности по умолчанию, остальные две — в плотности.

Высоты и отступы контролов

Токен Значение
--control-h-xs 20px
--control-h-sm 26px
--control-h-md 32px
--control-h-lg 38px
--control-pad-sm --space-4
--control-pad-md --space-5
--control-pad-lg --space-6

Отступы по назначению

Токен Значение
--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

Глифы и жёлобы

Токен Значение
--size-dot 6px
--size-check 15px
--size-switch-w 30px
--size-switch-h 18px
--size-thumb 14px
--size-track 4px
--size-meter 6px
--size-spinner 13px
--size-icon 16px
--size-chevron 10px
--size-marker 2px
--size-gutter 14px
--size-indent 14px
--label-col 92px

Раскладка

Токен Значение
--aside-w 232px
--container-max 76rem
--split-side 18rem
--split-main 30rem
--col-min 260px

Шкала

Не привязаны к теме и не перенастраиваются плотностью. Подробности — в типографике и ритме и форме.

Типографика

Группа Токены
Семейства --font-sans --font-mono
Кегли --text-2xs --text-xs --text-sm --text-md --text-lg --text-xl --text-2xl
Начертания --weight-normal --weight-medium
Интерлиньяж --leading-tight --leading-ui --leading-prose
Трекинг --tracking-tight --tracking-normal

Пространство и форма

Группа Токены
Шкала --space-1--space-10 (2 · 4 · 6 · 8 · 12 · 16 · 24 · 32 · 48 · 64)
Радиусы --radius-xs 3 · --radius-sm 5 · --radius-md 7 · --radius-lg 11 · --radius-full
Линии --hairline (0.5px от 1.5dppx, иначе 1px) · --stroke 1px

Движение

Группа Токены
Длительности --dur-1 80ms · --dur-2 140ms · --dur-3 220ms
Плавность --ease-out · --ease-in-out

Ярус 4 — компонент

Живёт не здесь, а рядом с компонентом. Правило одно: 2–3 переменные, не больше, и вариант описывается ими, а не переписыванием блока.

.my-btn--accent {
  --btn-fg: var(--accent-on);
  --btn-bg: var(--accent-solid);
}

Список переменных конкретного компонента — на его странице: например, у кнопки их пять, у таблицы — свои.

Варианты

Атрибут color-scheme --hue-neutral Ещё
нет по системе 75
data-theme="light-neutral" light --tint: 0 — уклон выключен
data-theme="light" light 75
data-theme="light-cool" light 250
data-theme="dark-light" dark 75 Стопка на две ступени вверх, плюс подписи статусов и дорожка
data-theme="dark-soft" dark 75 Переопределены --surface-sunken --surface-page --surface-raised --surface-overlay --surface-field
data-theme="dark" dark 75

Правила

Правило Следствие
Компонент видит ярусы 2 и 3 --n-3 в компоненте — захардкоженная светлая тема; 14px — сломанная плотность
У смыслового тона ровно два токена --*-text (метка) и --*-bg (фон). Третий не проходил порог на дорожке
Текст и метка — разные передние планы --tone-ink держит 4.5:1, --tone-mark — 3:1, но против двух фонов
Несущая и декоративная рамка — разные токены --border-control обязан 3:1, --border — нет
Дефолты объявляются через :where() Дефолт с той же специфичностью, что переопределение, — не дефолт
Новое значение — роль, а не константа Иначе оно не переживёт compact
Второго акцентного тона не бывает Это не запрос фичи, а сигнал, что смысл несёт что-то другое

Доступность

Проверка go -C tools run ./cmd/contrast читает настоящий tokens.css и резолвит light-dark(), color-mix() и var() так же, как браузер. Новая пара токенов добавляется туда вместе с токеном
Пороги Текст 4.5:1, метка 3:1 (против поверхности и против дорожки), несущая рамка 3:1
Шесть тем Каждая пара проверяется во всех шести, а не в двух
Кегли в rem Настройка «размер шрифта по умолчанию» в браузере работает; высоты применяются как min-block-size
Пол кегля --text-2xs, 11px. Ниже токена нет

API

Перечисляет генератор, читая src/tokens.css. Список не пишется руками и потому не может отстать от кита.

ИмяЗначениеЧто делает
токен
--a-1oklch(0.960 0.022 250)
--a-2oklch(0.800 0.100 250)
--a-3oklch(0.700 0.130 250)
--a-4oklch(0.560 0.155 250)
--a-5oklch(0.450 0.135 250)
--a-6oklch(0.330 0.100 250)
--accent-bglight-dark(var(--a-1), color-mix(in oklab, var(--a-4) 15%, transparent))
--accent-borderlight-dark(var(--a-4), var(--a-3))
--accent-hovervar(--a-5)
--accent-marklight-dark(var(--a-4), var(--a-3))
--accent-onvar(--n-0)
--accent-solidvar(--a-4)
--accent-textlight-dark(var(--a-5), var(--a-3))
--aside-w232px
--borderlight-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11))
--border-controllight-dark(oklch(0 0 0 / 0.46), oklch(1 0 0 / 0.38))
--border-stronglight-dark(oklch(0 0 0 / 0.22), oklch(1 0 0 / 0.20))
--border-subtlelight-dark(oklch(0 0 0 / 0.07), oklch(1 0 0 / 0.06))
--chart-1light-dark(oklch(0.520 0.150 280), oklch(0.700 0.130 280))
--chart-2light-dark(oklch(0.600 0.150 320), oklch(0.760 0.120 320))
--chart-3light-dark(oklch(0.550 0.140 355), oklch(0.720 0.120 355))
--chart-4light-dark(oklch(0.650 0.120 55), oklch(0.800 0.110 55))
--chart-5light-dark(oklch(0.580 0.130 115), oklch(0.740 0.120 115))
--chart-6light-dark(oklch(0.620 0.110 190), oklch(0.780 0.100 190))
--col-min260px
--container-max76rem
--control-h-lg38px
--control-h-md32px
--control-h-sm26px
--control-h-xs20px
--control-pad-lgvar(--space-6)
--control-pad-mdvar(--space-5)
--control-pad-smvar(--space-4)
--dur-180ms
--dur-2140ms
--dur-3220ms
--ease-in-outcubic-bezier(0.65, 0, 0.35, 1)
--ease-outcubic-bezier(0.22, 0.61, 0.36, 1)
--err-1oklch(0.955 0.025 25)
--err-2oklch(0.760 0.150 25)
--err-3oklch(0.725 0.160 25)
--err-4oklch(0.545 0.185 25)
--err-5oklch(0.500 0.185 25)
--err-6oklch(0.330 0.110 25)
--err-bglight-dark(var(--err-1), color-mix(in oklab, var(--err-4) 16%, transparent))
--err-marklight-dark(var(--err-4), var(--err-3))
--err-textlight-dark(var(--err-5), var(--err-3))
--focus-ringlight-dark(var(--a-4), var(--a-3))
--font-monoui-monospace, "Cascadia Code", "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", monospace
--font-sansui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif
--gap-inlinevar(--space-4)
--gap-rowvar(--space-3)
--hairline1px
--hue-neutral75
--label-col92px
--leading-prose1.65
--leading-tight1.25
--leading-ui1.4
--n-0oklch(0.994 calc(0.002 * var(--tint)) var(--hue-neutral))
--n-1oklch(0.978 calc(0.003 * var(--tint)) var(--hue-neutral))
--n-10oklch(0.322 calc(0.005 * var(--tint)) var(--hue-neutral))
--n-11oklch(0.242 calc(0.004 * var(--tint)) var(--hue-neutral))
--n-12oklch(0.196 calc(0.004 * var(--tint)) var(--hue-neutral))
--n-13oklch(0.155 calc(0.003 * var(--tint)) var(--hue-neutral))
--n-14oklch(0.120 calc(0.003 * var(--tint)) var(--hue-neutral))
--n-2oklch(0.958 calc(0.004 * var(--tint)) var(--hue-neutral))
--n-3oklch(0.928 calc(0.004 * var(--tint)) var(--hue-neutral))
--n-4oklch(0.884 calc(0.005 * var(--tint)) var(--hue-neutral))
--n-5oklch(0.806 calc(0.006 * var(--tint)) var(--hue-neutral))
--n-6oklch(0.706 calc(0.006 * var(--tint)) var(--hue-neutral))
--n-7oklch(0.606 calc(0.006 * var(--tint)) var(--hue-neutral))
--n-8oklch(0.508 calc(0.006 * var(--tint)) var(--hue-neutral))
--n-9oklch(0.416 calc(0.006 * var(--tint)) var(--hue-neutral))
--ok-1oklch(0.955 0.025 150)
--ok-2oklch(0.780 0.140 150)
--ok-3oklch(0.660 0.130 150)
--ok-4oklch(0.545 0.120 150)
--ok-5oklch(0.500 0.120 150)
--ok-6oklch(0.330 0.075 150)
--ok-bglight-dark(var(--ok-1), color-mix(in oklab, var(--ok-4) 16%, transparent))
--ok-marklight-dark(var(--ok-4), var(--ok-3))
--ok-textlight-dark(var(--ok-5), var(--ok-3))
--pad-cardvar(--space-6)
--pad-cell-xvar(--space-4)
--pad-panelvar(--space-5)
--radius-full999px
--radius-lg11px
--radius-md7px
--radius-sm5px
--radius-xs3px
--row-pad-yvar(--space-3)
--scrimlight-dark(oklch(0 0 0 / 0.32), oklch(0 0 0 / 0.58))
--shadow-color-farlight-dark(oklch(0 0 0 / 0.10), oklch(0 0 0 / 0.45))
--shadow-color-nearlight-dark(oklch(0 0 0 / 0.06), oklch(0 0 0 / 0.30))
--shadow-modal0 16px 40px -8px var(--shadow-color-far), 0 4px 10px -4px var(--shadow-color-near)
--shadow-popover0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near)
--size-check15px
--size-chevron10px
--size-dot6px
--size-gutter14px
--size-icon16px
--size-indent14px
--size-marker2px
--size-meter6px
--size-spinner13px
--size-switch-h18px
--size-switch-w30px
--size-thumb14px
--size-tick3px
--size-track4px
--space-12px
--space-1064px
--space-24px
--space-36px
--space-48px
--space-512px
--space-616px
--space-724px
--space-832px
--space-948px
--split-main30rem
--split-side18rem
--stroke1px
--surface-activelight-dark(oklch(0 0 0 / 0.065), oklch(1 0 0 / 0.080))
--surface-fieldlight-dark(var(--n-1), var(--n-14))
--surface-hoverlight-dark(oklch(0 0 0 / 0.035), oklch(1 0 0 / 0.045))
--surface-overlaylight-dark(var(--n-0), var(--n-11))
--surface-pagelight-dark(var(--n-1), var(--n-13))
--surface-raisedlight-dark(var(--n-0), var(--n-12))
--surface-selectedcolor-mix(in oklab, var(--a-4) 14%, transparent)
--surface-sunkenlight-dark(var(--n-2), var(--n-14))
--tap-min24px
--text-2xl1.6875rem
--text-2xs0.6875rem
--text-faintlight-dark(var(--n-7), var(--n-7))
--text-lg1.0625rem
--text-md0.9375rem
--text-mutedlight-dark(var(--n-8), var(--n-6))
--text-primarylight-dark(var(--n-12), var(--n-1))
--text-secondarylight-dark(var(--n-9), var(--n-5))
--text-sm0.8125rem
--text-xl1.3125rem
--text-xs0.75rem
--tint1
--tracklight-dark(oklch(0 0 0 / 0.10), oklch(1 0 0 / 0.16))
--tracking-normal0
--tracking-tight-0.01em
--warn-1oklch(0.960 0.040 85)
--warn-2oklch(0.860 0.145 85)
--warn-3oklch(0.760 0.140 85)
--warn-4oklch(0.620 0.135 85)
--warn-5oklch(0.520 0.135 85)
--warn-6oklch(0.360 0.080 85)
--warn-bglight-dark(var(--warn-1), color-mix(in oklab, var(--warn-4) 16%, transparent))
--warn-marklight-dark(var(--warn-4), var(--warn-3))
--warn-textlight-dark(var(--warn-5), var(--warn-3))
--weight-medium500
--weight-normal400
--z-modal1000
--z-popover100
--z-sticky10

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