Произвольные значения

Разовое значение прямо в имени класса: gr-mt-[13px]. Правило собирает рантайм — статический файл выразить это не может, потому что список возможных значений неизвестен до того, как страница написана.

Требует JS packages/utils/src/griffincss-utils.js

Без JS не работает — и не может работать. Класса .gr-mt-[13px] в собранном CSS нет: правило создаётся в момент, когда рантайм видит элемент. Если скрипт не подключён или не выполнился, отступа не будет. Поэтому произвольным значением задаётся оформление, а не то, от чего зависит читаемость страницы: шкала .gr-mt-4 работает всегда, и она остаётся основным способом.

Как это выглядит

<script src="griffincss.js"></script>
<script src="griffincss-utils.js"></script>

<div class="gr-mt-[13px]">отступ сверху 13px</div>
<div class="gr-w-[42%]">ширина 42%</div>
<div class="gr-p-[calc(1rem+3px)]">паддинг из calc()</div>

Рантайм разбирает имя класса, находит свойство по части до скобки и добавляет правило в тот же слой griffincss.utils, где живут обычные утилиты. Значит, произвольное значение спорит с классом из шкалы по порядку в слое, а не по весу селектора, — как и любые два класса библиотеки между собой.

/* что оказывается в <style id="griffincss-dynamic"> */
@layer griffincss.utils {
  .gr-mt-\[13px\]{margin-top:13px}
  .gr-w-\[42\%\]{width:42%}
}

Обратные слэши в селекторе рантайм проставляет сам: без экранирования браузер прочитал бы .gr-mt-[13px] как класс gr-mt- и следом селектор по атрибуту — правило не совпало бы ни с чем.

Результат
gr-radius-[14px] + gr-p-[13px]
gr-mt-[13px] + gr-w-[42%]

Какие свойства доступны

Таблица свойств повторяет объявления статического класса один в один: .gr-p-4 пишет и --gr-p, и padding, — и .gr-p-[13px] пишет столько же. Иначе каскад скруглений видел бы зазор у одного класса и не видел у другого.

КлассСвойство
.gr-m-[…], .gr-mt-[…], .gr-mb-[…]margin, верх и низ
.gr-ms-[…], .gr-me-[…], .gr-mx-[…], .gr-my-[…]логические и осевые маржины
.gr-p-[…], .gr-pt-[…], .gr-pb-[…]padding, верх и низ
.gr-ps-[…], .gr-pe-[…], .gr-px-[…], .gr-py-[…]логические и осевые паддинги
.gr-radius-[…]--gr-r + border-radius
.gr-gap-[…]gap
.gr-w-[…], .gr-h-[…]width, height
.gr-min-w-[…], .gr-max-w-[…], .gr-min-h-[…], .gr-max-h-[…]минимумы и максимумы
.gr-top-[…], .gr-bottom-[…], .gr-start-[…], .gr-end-[…], .gr-inset-[…]смещения; start/end — логические, вдоль строки
.gr-z-[…]z-index

Почему нет .gr-text-[…]. У префикса gr-text- два смысла: .gr-text-2xl — это кегль, а .gr-text-white — цвет. Что означает .gr-text-[13px], пришлось бы угадывать, а рантайм не угадывает. Цвета и кегль задаются своим CSS или переменной --gr-* на элементе.

Что считается невалидным

Содержимое скобок приходит из разметки, поэтому проверяется так же, как строка раскладки в ядре. Невалидное даёт console.warn и пропуск класса — не сломанное правило, за которым в таблице стилей идут все остальные.

ОтклоняетсяПример
пустые скобкиgr-mt-[]
значение длиннее 48 символовgr-mt-[calc(1px+1px+…)]
{, } или ; в значенииgr-mt-[1px;color:red]
комментарий /* в значенииgr-mt-[1px/*x*/]
свойство неизвестно таблицеgr-zzz-[13px]

Пробела в значении не бывает по устройству разметки: атрибут class разделяется пробелами, и gr-mt-[1px + 2px] распался бы на три класса. Составное значение пишется без пробелов: gr-mt-[calc(1px+2px)].

Вместе с каскадом скруглений

Произвольные .gr-radius-[…] и .gr-p-[…] участвуют в каскаде скруглений наравне со ступенями шкалы: рантайм читает их как объявленные значения и выводит радиус вложенного .gr-radius по той же рекурренте.

<div class="gr-radius gr-radius-[2rem] gr-p-[8px]">
  <div class="gr-radius">радиус max(0px, calc(2rem - 8px))</div>
</div>
Результат
Вложенный блок — радиус выведен из родителя

Отсечение неиспользуемого

npm run purge произвольных значений не касается: в статическом файле их правил нет, отсекать нечего, и в safelist их вносить не нужно. Safelist нужен для обратного случая — классов из шкалы, которые собираются в коде склейкой ('gr-mt-' + n).

Когда лучше не надо

Примеры использования

Произвольные значения раскрывают возможности, которых у шкал не бывает в принципе: отрицательные смещения, calc(), единицы ch и точные пиксели макета. Рецепты ниже — три задачи, которые без скобок не решаются вовсе.

Счётчик за углом кнопки

Шкала привязки к краям знает только 0 и auto — наружу за край она не выходит. Отрицательные смещения .gr-top-[-0.5rem] .gr-end-[-0.5rem] выносят бейдж-счётчик наполовину за угол кнопки — как у иконки уведомлений. Родителю — .gr-relative .gr-w-fit. Остальные выносы за край — колонка целиком снаружи, ярлык на ребре — разобраны в «Выносе за край» на странице Position.

<span class="gr-relative gr-w-fit gr-inline-block">
  <button class="gr-btn gr-btn-secondary">Уведомления</button>
  <span class="gr-badge gr-badge-danger gr-absolute gr-top-[-0.5rem] gr-end-[-0.5rem]">3</span>
</span>
За край выводят отрицательные .gr-top-[…]/.gr-end-[…]
3

Рабочая область «экран минус шапка»

Чат, редактор, карта: под фиксированной шапкой область должна занять ровно остаток экрана и прокручиваться внутри себя. .gr-h-[calc(100svh-56px)] считает это прямо в классе — calc() пишется без пробелов, иначе атрибут class разрежет его на части. В связке — .gr-overflow-y-auto для внутренней прокрутки.

<header style="height:56px">…</header>
<main class="gr-h-[calc(100svh-56px)] gr-overflow-y-auto">
  …лента чата со своим скроллом…
</main>
В демо тот же приём в миниатюре: calc(12rem-40px) при обёртке 12rem
Шапка — 40px

Область получила ровно остаток высоты и прокручивается сама.

Сообщение 2

Сообщение 3

Сообщение 4

Сообщение 5

Сообщение 6

Панель точной ширины из макета

Дробные ширины хороши, пока панель может дышать; сайдбар из макета — это ровно 280px на любом экране. .gr-w-[280px] фиксирует панель, сосед забирает остальное знакомой парой .gr-flex-1 .gr-min-w-0. Если 280px повторяется в проекте много раз — это уже не произвольное значение, а своя переменная (см. «Когда лучше не надо»).

<div class="gr-flex gr-gap">
  <aside class="gr-w-[280px]">Панель из макета</aside>
  <main class="gr-flex-1 gr-min-w-0">Контент</main>
</div>
Панель — ровно 280px, остаток — .gr-flex-1

Контент

Занимает всё остальное.