Стратегии оформления

Четыре стратегии оформления переключаются одним атрибутом на корне документа. Ось независима от цветовой темы и от режима для слабовидящих: любое из сочетаний обязано работать. Переключатель — в боковой панели слева, он действует на всех страницах документации.

<html data-gr-theme="dark" data-gr-style="strict" data-gr-a11y="low-vision">
стиль тема действует доступность

Четыре стратегии

ЗначениеНастроениеЧем отличается
standard Базовое Скруглённые границы, цветные кнопки. Равносильно отсутствию атрибута
airy Воздушное Скругления крупнее, просторнее, пастельный акцент, отклик с лёгким перелётом
strict Строгое Углы почти прямые, тени до наметки, границы бледнее, фоны к краю шкалы
compact Журнальное То же, что строгое, но плотнее: меньше зазоры, ниже интерлиньяж, разделители строк в таблице

Как это устроено

Страница ничего не знает о действующем стиле. Ось меняет значения нескольких ручек — --gr-radius, --gr-density, --gr-elevation, --gr-transition и ещё несколько, — а компоненты читают их через var(). Цвет при этом сдвигается не абсолютными значениями, а якорями, которые задаёт действующая тема: поэтому строгий стиль осветляет фоны на светлой теме и делает их графитовыми на тёмной, оставаясь одним и тем же блоком правил.

Утилиты отступов ось не двигает. .gr-p-6 остаётся 1.5rem в любом стиле: утилита — явное указание автора страницы, и стилю не место в нём. Плотность действует на собственные отступы компонентов и на --gr-gap.

Что стиль переводит в палитре

Стиль — не только геометрия и движение. Каждый из трёх переводит часть HSL-триплетов темы на собственные якоря; значения якорей задаёт действующая тема, поэтому один и тот же блок стиля работает поверх светлой и тёмной. Ниже — полный перечень: какой токен и на какой якорь переведён. Прочерк — стиль этот токен не трогает. Таблица написана руками, а тест style-css.test.js сверяет её с миксинами gr-style-*: токен, который стиль переводит, а таблица не называет, роняет сборку.

Токенairystrictcompact
--gr-hsl-accentaccent-softaccent-press
--gr-hsl-accent-hoveraccent-soft-hoveraccent-press-hover
--gr-hsl-on-accenton-accent-soft
--gr-hsl-linkaccent-softaccent-press
--gr-hsl-link-hoveraccent-soft-hoveraccent-press-hover
--gr-hsl-surface-0surface-0-tintedsurface-0-liftedsurface-0-lifted
--gr-hsl-surface-1surface-1-tintedsurface-1-liftedsurface-1-lifted
--gr-hsl-surface-2surface-2-tintedsurface-2-liftedsurface-2-lifted
--gr-hsl-surface-sunkensurface-sunken-tintedsurface-sunken-liftedsurface-sunken-lifted
--gr-hsl-surface-overlaysurface-overlay-tintedsurface-1-liftedsurface-1-lifted
--gr-hsl-borderborder-tintedborder-softborder-press
--gr-hsl-border-strongborder-strong-tintedborder-strong-softborder-strong-press
--gr-hsl-status-successstatus-success-press
--gr-hsl-status-success-surfacestatus-success-pastelstatus-success-press
--gr-hsl-on-status-successon-status-success-pastel
--gr-hsl-status-warning-surfacestatus-warning-pastelstatus-warning-surface-press
--gr-hsl-on-status-warningon-status-warning-pastel
--gr-hsl-status-dangerstatus-danger-press
--gr-hsl-status-danger-surfacestatus-danger-pastelstatus-danger-press
--gr-hsl-on-status-dangeron-status-danger-pastel
--gr-hsl-status-infostatus-info-press
--gr-hsl-status-info-surfacestatus-info-pastelstatus-info-press
--gr-hsl-on-status-infoon-status-info-pastel

Текстовые цвета статусов (--gr-hsl-status-warning и соседи) воздушный и строгий не трогают: сигнал в тексте обязан остаться контрастным, пастельным становится прямоугольник. Журнальный приглушает и их — вместе с линиями, это его характер. Режим для слабовидящих перебивает всё перечисленное: он возвращает поверхности, чернила, линии и акцент к якорям контраста, а чернила на акценте — к surface-max.

Отсюда же следует, где бренду задавать свои цвета: воздушный стиль берёт пару --gr-hsl-accent-soft / -soft-hover и чернила --gr-hsl-on-accent-soft, журнальный — --gr-hsl-accent-press / -press-hover, а повседневный и контрастный акцент — якоря -base и -max темы. Рецепт целиком — «Темы».

Образцы

Кнопки

Карточки

Шапка карточки

В строгом стиле шапка теряет подложку и остаётся линией, в воздушном карточка отделяется тенью вместо границы.

Приподнятая карточка: плотность тени задаёт стиль.

Бейджи и статусы

Обычный Таблетка Контурный Успех Внимание Опасность

У каждого статуса три роли: цвет текста, цвет заливки и цвет чернил на этой заливке. Стиль меняет заливку и чернила на ней, а текстовый цвет оставляет тёмным — «опасность» в тексте на белом фоне пастельной быть не может. Воздушный смягчает заливки, журнальный приглушает их, строгий не трогает вовсе.

Красная кнопка в воздушном стиле — единственная со светлой подписью: белый текст на красном устоявшийся знак отказа, и ради единообразия с соседями его ломать не стоит. Заливка ради этого держится темнее прочих. Жёлтая в журнальном приглушается в светлую сторону, а не в тёмную: «внимание» держится на узнаваемости оттенка, и потемневший жёлтый читается как оливковый.

Поле ввода

Уведомление

Плотность действует и здесь
В журнальном стиле полоса уведомления заметно уже.

Таблица

РучкаСтандартныйСтрогийЖурнальный
--gr-radius0.5rem0.125rem0.1875rem
--gr-density110.8
--gr-elevation10.40.3
--gr-transition0.2s ease0.12s ease-out0.1s ease-out

Стиль на любом элементе

Атрибут работает не только на <html>. Островок ниже несёт собственный стиль и собственную тему — это и есть проверка того, что ось пересчитывает цвета, метрики и тени на своём носителе, а не наследует готовые значения страницы.

<div data-gr-style="compact">
Таблетка
<div data-gr-style="airy" data-gr-theme="dark">

Тёмная тема и воздушный стиль на одном островке.

<div data-gr-style="standard">

standard — валидное значение атрибута, а не только «атрибут не указан». Островок возвращает геометрию и движение: скругление снова 0.5rem, плотность единичная. Но цвет он наследует от стиля страницы — выберите воздушный, и подложка здесь останется подкрашенной.

<div data-gr-style="standard" data-gr-theme="light">

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

Приоритет осей

Тема задаёт цвет, стиль — геометрию и движение, режим для слабовидящих перебивает обоих. Включите тумблер «Для слабовидящих» вместе с журнальным стилем: интерфейс не ужмётся, а границы станут толще. Воздушный стиль при этом простора не теряет — отменяется только тот стиль, который ужимает, а «крупнее и просторнее» режиму доступности не противоречит.

Подключение

Стандартный стиль работает без единой дополнительной строки — он и есть базовые значения :root. Три остальных приезжают отдельным файлом:

<link rel="stylesheet" href="griffincss-core.css">
<link rel="stylesheet" href="griffincss-styles.css">

Переключение из скрипта:

Griffincss.theme.style('strict');   // 'standard' | 'airy' | 'strict' | 'compact'
Griffincss.theme.get();             // {theme, resolved, style, a11y, resolvedA11y}

Возьмите один стиль

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

@use 'griffincss-core/scss/style-config' with ($gr-styles: (strict));

@use 'griffincss-core/scss/griffincss-styles';
@use 'griffincss-ui';

Пустой список выключает ось целиком — ни блоков токенов в файле оси, ни правил [data-gr-style] в компонентах:

@use 'griffincss-core/scss/style-config' with ($gr-styles: ());

Что это даёт в байтах (brotli, замер сборкой):

$gr-styles griffincss-styles.css griffincss-ui.css
по умолчанию — все три1 566 Б8 845 Б
(airy)1 232 Б8 774 Б
(strict)971 Б8 775 Б
(compact)1 188 Б8 780 Б
() — ось выключена796 Б*8 730 Б

* Файл при пустом списке не исчезает: 796 Б — общая часть оси, правила-носители [data-gr-style] без единого блока токенов. Ось не нужна — файл просто не подключают, и не приезжает ни байта.

Экономия здесь небольшая, и это честная цифра. Один стиль вместо трёх — около 350 Б brotli в файле оси и ещё 70 Б в компонентах, на фоне 18 КБ всего CSS библиотеки. Настоящий выбор проходит не между одним стилем и тремя, а между «ось подключена» и «не подключена»: griffincss-styles.css — отдельный файл, и тем, кому ось не нужна, она не стоит ничего. Ради 350 байт ломать себе сборку не стоит; ради того, чтобы не тянуть чужие стили в продукт, где выбран один, — вполне.

standard в список не входит и не должен. Стандартный стиль — это базовые значения :root в ядре, объявлять ему нечего: он работает и при $gr-styles: (). Значением атрибута он при этом остаётся — <section data-gr-style="standard"> возвращает базовую геометрию внутри страницы с другим стилем. Вписанный в список standard роняет сборку с @error, как и любое другое неизвестное имя: список знает ровно три — airy, strict, compact.

Готовый файл на один стиль

Тому, кто SCSS не компилирует — берёт файлы с CDN или из dist/, — настроить список негде. Для него те же три конфигурации собраны заранее, по файлу на стиль:

<!-- все три стиля -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/griffincss-core@0.26.1/dist/griffincss-styles.css">

<!-- или один — вместо предыдущей строки, а не вдобавок к ней -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/griffincss-core@0.26.1/dist/griffincss-style-strict.css">
ФайлСтилиВесbrotli
griffincss-styles.cssвсе три13 360 Б1 566 Б
griffincss-style-airy.cssairy9 436 Б1 232 Б
griffincss-style-strict.cssstrict6 997 Б971 Б
griffincss-style-compact.csscompact9 153 Б1 188 Б

Из npm те же файлы лежат в node_modules/griffincss-core/dist/. Каждый из них — замена griffincss-styles.css, а не добавка к нему: общая часть оси входит в файл целиком, и два таких файла разом привезут её дважды.

Структурные правила компонентов однофайловая сборка не трогает. Правила [data-gr-style] в griffincss-ui.css — другой пакет, и в готовом файле остаются все четыре стиля. Стоят они 115 Б brotli на всех, поэтому отдельных сборок компонентов ради них не заводится: нужно отсечь и их — соберите griffincss-ui из SCSS с тем же списком или пропустите готовый файл через npm run purge.

Отсечение: за чужие стили можно не платить

Собирать из SCSS ради одного стиля не обязательно. npm run purge читает data-gr-style в разметке и выбрасывает правила и блоки токенов остальных стилей — без единой настройки. Замер на странице со стилем compact: griffincss-styles.css 1 564 → 1 365 Б brotli, структурные правила в griffincss-ui.css 2 717 → 2 644 Б. Если data-gr-style в разметке не встретился вовсе, ось уходит целиком: 1 566 → 519 Б.

Стиль, который ставится в рантайме, скрипт не видит. Griffincss.theme.style('strict') в файле с кодом найдётся строковым литералом, а тот же вызов в инлайновом <script> внутри HTML — нет. Имя такого стиля добавляется в safelist наравне с классами: npm run purge -- --safelist strict …. Отчёт называет и оставленные стили, и выброшенные — по нему и видно, что safelist собран верно.

В scoped-сборке структурных правил нет. griffincss-ui-scoped.css ограничен областью .griffin, а атрибут стоит на <html>: селектор там получает префикс :where(.griffin) и требует носитель атрибута внутри области. Ось там действует на уровне токенов — скругления, плотность, тени и цвета работают полностью, — но шапка карточки не превращается в линию.

Порог браузеров у этих файлов при этом тот же, что у обычных сборок: :where() знают Chrome 88, Safari 14 и Firefox 78 — ниже каскадных слоёв. Замер и метод — в разделе «Совместимость».

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

Ось стилей — инструмент двух ситуаций: разные продукты на одной сборке и разные зоны одной страницы. Оба рецепта ниже работают вживую.

Промо-врезка, которая дышит иначе

Приложение живёт в строгом стиле, а маркетинговой врезке — подписке, анонсу тарифа — хочется воздуха. Атрибут на самой врезке решает это без вторых версий компонентов: data-gr-style="airy", и те же кнопка, поле и бейдж внутри становятся мягче — крупнее скругления, пастельный акцент, отклик с перелётом. Разметка не отличается ни классом.

<aside data-gr-style="airy" class="gr-card gr-p-6">
  <span class="gr-badge gr-badge-pill">Новое</span>
  <h3>Дайджест раз в неделю</h3>
  <div class="gr-input-group">
    <input class="gr-input" type="email" placeholder="Почта" aria-label="Почта">
    <button class="gr-btn gr-btn-primary">Подписаться</button>
  </div>
</aside>
Врезка data-gr-style="airy" — та же разметка, другой характер

Плотность — на выбор пользователя

Таблично-ориентированные интерфейсы часто дают переключатель «просторно / компактно». Ось стилей делает его двумя строками: журнальный стиль и есть «компактно» — плотнее зазоры, ниже интерлиньяж, разделители в таблицах. Кнопка ниже переключает всю эту страницу — панель показаний наверху отреагирует тоже.

<button id="density" type="button">Компактно</button>

<script>
  density.addEventListener('click', function () {
    var next = Griffincss.theme.get().style === 'compact' ? 'standard' : 'compact';
    Griffincss.theme.style(next);
    density.textContent = next === 'compact' ? 'Просторно' : 'Компактно';
  });
</script>

— рабочая кнопка: журнальный стиль ⇄ стандартный.