Стратегии оформления
Четыре стратегии оформления переключаются одним атрибутом на корне документа. Ось независима от цветовой темы и от режима для слабовидящих: любое из сочетаний обязано работать. Переключатель — в боковой панели слева, он действует на всех страницах документации.
<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-*: токен, который стиль переводит, а таблица
не называет, роняет сборку.
| Токен | airy | strict | compact |
|---|---|---|---|
--gr-hsl-accent | accent-soft | — | accent-press |
--gr-hsl-accent-hover | accent-soft-hover | — | accent-press-hover |
--gr-hsl-on-accent | on-accent-soft | — | — |
--gr-hsl-link | accent-soft | — | accent-press |
--gr-hsl-link-hover | accent-soft-hover | — | accent-press-hover |
--gr-hsl-surface-0 | surface-0-tinted | surface-0-lifted | surface-0-lifted |
--gr-hsl-surface-1 | surface-1-tinted | surface-1-lifted | surface-1-lifted |
--gr-hsl-surface-2 | surface-2-tinted | surface-2-lifted | surface-2-lifted |
--gr-hsl-surface-sunken | surface-sunken-tinted | surface-sunken-lifted | surface-sunken-lifted |
--gr-hsl-surface-overlay | surface-overlay-tinted | surface-1-lifted | surface-1-lifted |
--gr-hsl-border | border-tinted | border-soft | border-press |
--gr-hsl-border-strong | border-strong-tinted | border-strong-soft | border-strong-press |
--gr-hsl-status-success | — | — | status-success-press |
--gr-hsl-status-success-surface | status-success-pastel | — | status-success-press |
--gr-hsl-on-status-success | on-status-success-pastel | — | — |
--gr-hsl-status-warning-surface | status-warning-pastel | — | status-warning-surface-press |
--gr-hsl-on-status-warning | on-status-warning-pastel | — | — |
--gr-hsl-status-danger | — | — | status-danger-press |
--gr-hsl-status-danger-surface | status-danger-pastel | — | status-danger-press |
--gr-hsl-on-status-danger | on-status-danger-pastel | — | — |
--gr-hsl-status-info | — | — | status-info-press |
--gr-hsl-status-info-surface | status-info-pastel | — | status-info-press |
--gr-hsl-on-status-info | on-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-radius | 0.5rem | 0.125rem | 0.1875rem |
| --gr-density | 1 | 1 | 0.8 |
| --gr-elevation | 1 | 0.4 | 0.3 |
| --gr-transition | 0.2s ease | 0.12s ease-out | 0.1s ease-out |
Стиль на любом элементе
Атрибут работает не только на <html>.
Островок ниже несёт собственный стиль и собственную тему — это и есть
проверка того, что ось пересчитывает цвета, метрики и тени на своём
носителе, а не наследует готовые значения страницы.
Тёмная тема и воздушный стиль на одном островке.
standard — валидное значение атрибута, а не только «атрибут
не указан». Островок возвращает геометрию и движение: скругление снова
0.5rem, плотность единичная. Но цвет он наследует от стиля
страницы — выберите воздушный, и подложка здесь останется подкрашенной.
Полный сброс требует обеих осей. Это не пробел, а граница ответственности: цвет задаёт тема, стиль его лишь сдвигает, и вернуть затёртые значения стилю неоткуда — кроме как продублировав у себя всю палитру темы.
Приоритет осей
Тема задаёт цвет, стиль — геометрию и движение, режим для слабовидящих перебивает обоих. Включите тумблер «Для слабовидящих» вместе с журнальным стилем: интерфейс не ужмётся, а границы станут толще. Воздушный стиль при этом простора не теряет — отменяется только тот стиль, который ужимает, а «крупнее и просторнее» режиму доступности не противоречит.
Подключение
Стандартный стиль работает без единой дополнительной строки — он и есть
базовые значения :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.css | airy | 9 436 Б | 1 232 Б |
griffincss-style-strict.css | strict | 6 997 Б | 971 Б |
griffincss-style-compact.css | compact | 9 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>
— рабочая кнопка: журнальный стиль ⇄ стандартный.