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

Основания

Высота и поверхности

Глубину передаёт порядок светлоты. Тень означает здесь одно — «плавает сверху и сейчас исчезнет», — и носителей у неё два: поповер и модалка.

Компонент
Прогон #4127
worldgen-01
Карточка на панели: ступень поверхности, ни одной тени
Разметка
<div class="inst-panel">
  <div class="inst-panel-header">
    <span class="inst-panel-title">Прогон #4127</span>
  </div>
  <div class="inst-panel-body inst-stack">
    <div class="inst-card">
      <div class="inst-card-title">worldgen-01</div>
      <div class="inst-card-sub">Карточка на панели: ступень поверхности, ни одной тени</div>
    </div>
    <input class="inst-input" type="text" value="terrain_chunk_04">
  </div>
</div>

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

Глубину передаёт поверхность. Тень означает «плавает сверху и сейчас исчезнет» и принадлежит трём компонентам.

.my-panel {
  background: var(--surface-raised);
  border: var(--hairline) solid var(--border);
}
Что Обязательно Почему
Поверхность из семантики да Четыре ступени названы работой, а не номером: страница, врез, поднятая, оверлей
Тень только у всплывающего да Поповер, модалка, уведомление. Четвёртого носителя тени в ките нет
Рамка CanvasText в forced-colors да Поверхности в этом режиме схлопываются, и без рамки блок сливается с фоном
z-index из словаря да Четыре токена. Число, выдуманное на месте, работает до первого чужого числа побольше

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

Используйте Возьмите другое
Перепад поверхностей для постоянной структуры Тень у карточки или панели — тень означает «временное». Структура строится карточкой и панелью
--shadow-popover для того, что исчезнет по Escape Тень «для объёма» — у неё нет получателя, кроме поповера
--shadow-modal + --scrim для того, что блокирует экран Поповер вместо модалки, если решение обязательно — берите модалку
--surface-field под ввод --surface-raised под ввод — поле сольётся с панелью, см. поле
Размытие фона под оверлеем Размытий кит не знает вовсе — затемнение делает --scrim

Устройство

Объявлен в одном месте, чтобы не разъезжался по компонентам.

Токен Значение Кто
--z-sticky 10 Липкая шапка таблицы, полоса действий формы
--z-popover 100 Поповер, меню, тултип
--z-modal 1000 Модалка, шторка

Поповер и модалка на Popover API и <dialog> живут в верхнем слое браузера, где z-index уже не решает. Токены остаются для того, что верхнего слоя не получает, — и для приложения, которому нужно встроиться в тот же порядок.

Шкала

Читаются как стопка снизу вверх. Ступень выбирается по роли области, а не по желаемой яркости.

Токен Ступень Кто носит
--surface-sunken Дно Дорожка, шапка таблицы, блок кода, тело шага, фон бокового столбца
--surface-page Страница body, фон рабочей области
--surface-raised Поднято Панель, карточка, шапка оболочки, полоса действий формы, <kbd>
--surface-overlay Оверлей Поповер, меню, тултип, модалка, шторка

Пятая роль стоит вне стопки, потому что она не про глубину, а про ввод:

Токен Работа
--surface-field Врез под поле. Отдельная роль, а не переиспользование raised: пока поле красилось цветом панели, его единственной границей была рамка на 1.31:1 — то есть поля на панели не было видно вообще

В тёмной теме между соседними ступенями расстояние шире, чем в светлой: тёмный конец рампы различается хуже, и зеркальная светлая раскладка дала бы четыре одинаковых чёрных прямоугольника. В теме dark-soft вся стопка поднимается на шаг рампы — переопределяются ровно пять токенов, а остальная семантика не знает, что тема сменилась. В dark-light она поднимается на две ступени и упирается в потолок: overlay там совпадает с raised, потому что следующий шаг рампы уже не держит текст. Ровно так же ведёт себя светлый конец, где обе роли сидят на --n-0.

Состояния поверх поверхности

Наведение и выделение — альфа. Непрозрачная заливка не давала бы им складываться: наведение по выделенной строке пропадало бы.

Токен Работа
--surface-hover Наведение
--surface-active Нажатие
--surface-selected Выделенная строка

Варианты

Обе функциональны. Обе объявлены целиком в токенах — компонент подставляет готовое значение и не собирает тень сам.

Токен Значение Кто носит
--shadow-popover 0 4px 12px -2px дальняя + 0 2px 4px -2px ближняя Поповер, меню, тултип
--shadow-modal 0 16px 40px -8px дальняя + 0 4px 10px -4px ближняя Модалка, шторка

Каждая тень двухслойная: дальняя даёт расстояние до фона, ближняя — контакт края. Одним слоем это выглядит либо приклеенным, либо парящим без опоры.

Цвет тени вынесен отдельно, потому что light-dark() принимает только <color> и целое значение тени через неё не проходит:

Токен Светлая Тёмная
--shadow-color-near oklch(0 0 0 / 0.06) oklch(0 0 0 / 0.30)
--shadow-color-far oklch(0 0 0 / 0.10) oklch(0 0 0 / 0.45)

В тёмной теме тень гуще. Слабая тень на тёмном фоне невидима, а светлая тень — это уже свечение, которого кит не знает.

Правило

Тень получает только то, что появилось по действию и исчезнет по Escape. Всё, что стоит на экране постоянно, отделяется поверхностью и рамкой. Карточка не получает тень — она получает поверхность.

Элемент Тень
Панель, карточка, таблица, полоса действий Нет
Поповер, меню, тултип --shadow-popover
Модалка, шторка --shadow-modal
Всё остальное Нет, и нового получателя не заводится

Подложка

Токен Светлая Тёмная
--scrim oklch(0 0 0 / 0.32) oklch(0 0 0 / 0.58)

В тёмной теме подложка гуще: на светлой странице затемнение само по себе создаёт контраст, на тёмной его приходится добавлять. Это затемнение, а не размытие — градиенты, свечения и размытия кит запрещает целиком.

Подложка ставится на ::backdrop нативного <dialog>, поэтому верхний слой, захват фокуса и закрытие по Escape приходят от платформы, а не от кита.

Доступность

Глубина не только цветом Каждая ступень поверхности сопровождается рамкой или перепадом ритма: различение стопки не требует различения светлот
Контраст ступеней Соседние поверхности проверены как пара во всех шести темах — иначе поле на панели пропадает
Тень не несёт смысла в одиночку Оверлей опознаётся ролью и поведением (popover, <dialog>), а не тенью. В режиме принудительных цветов тень исчезает, и это ничего не ломает
Прокрутка под модалкой Останавливается: showModal() делает фон инертным для кликов, но не для колеса, и пользователь терял место, к которому вернётся
Уменьшенное движение Появление оверлея схлопывается до 0.01ms, но не выключается — машины состояний, слушающие transitionend, продолжают работать. См. движение

API

ИмяЗначениеЧто делает
токен
--surface-sunkenlight-dark(var(--n-2), var(--n-14))
--surface-pagelight-dark(var(--n-1), var(--n-13))
--surface-raisedlight-dark(var(--n-0), var(--n-12))
--surface-overlaylight-dark(var(--n-0), var(--n-11))
--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-activelight-dark(oklch(0 0 0 / 0.065), oklch(1 0 0 / 0.080))
--surface-selectedcolor-mix(in oklab, var(--a-4) 14%, transparent)
--shadow-popover0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near)
--shadow-modal0 16px 40px -8px var(--shadow-color-far), 0 4px 10px -4px var(--shadow-color-near)
--shadow-color-nearlight-dark(oklch(0 0 0 / 0.06), oklch(0 0 0 / 0.30))
--shadow-color-farlight-dark(oklch(0 0 0 / 0.10), oklch(0 0 0 / 0.45))
--scrimlight-dark(oklch(0 0 0 / 0.32), oklch(0 0 0 / 0.58))
--z-sticky10
--z-popover100
--z-modal1000
--borderlight-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11))
--border-subtlelight-dark(oklch(0 0 0 / 0.07), oklch(1 0 0 / 0.06))
--hairline1px

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