Сообщения
Всё, чем интерфейс отвечает на действие: сообщение о статусе, полоса выполнения, шкала, рейтинг, кольцо занятости и всплывающее уведомление. Значение везде читается словом, а не только цветом.
Сообщение о статусе
| Класс | Назначение |
|---|---|
.gr-alert | Сообщение целиком; по умолчанию — информационное |
.gr-alert-{info,success,warning,danger} | Статус: полоса, рамка, подложка и цвет заголовка |
.gr-alert-title | Слово статуса — обязательная часть, а не украшение |
.gr-alert-icon | Слот под иконку: <svg>, эмодзи, любой знак |
.gr-alert-body | Текст; забирает остаток строки |
.gr-alert-dismissible | Отступ под кнопку закрытия в конце строки |
Почему заголовок обязателен. Цветная полоса не переживает
чёрно-белую печать, дальтонизм и монохромный режим высокого контраста.
Слово переживает. Поэтому у каждого сообщения есть
.gr-alert-title — «Ошибка», «Готово», «Внимание», — а цвет
и иконка остаются вторым и третьим признаком поверх него, а не вместо.
Это прямой вывод фазы 6a.
npm run build.
check-dist без замечаний.
.gr-select найден и в ui, и в utils.
<div class="gr-alert gr-alert-danger" role="alert">
<span class="gr-alert-icon" aria-hidden="true">✕</span>
<div class="gr-alert-body">
<strong class="gr-alert-title">Ошибка</strong>
Форму не удалось отправить.
</div>
</div>
Слово и полоса красятся разными токенами. Заголовок
берёт --gr-alert-color — цвет, безопасный для текста;
полоса и подложка берут --gr-alert-fill. У трёх статусов
из четырёх это одно и то же. Расходятся они у предупреждения: чтобы
читаться текстом на белом, его цвет обязан быть тёмно-коричневым,
а полоса такого цвета не читается как «внимание». Поэтому слово тёмное,
а полоса и подложка — янтарные.
Роль объявляется разметкой
| Атрибут | Когда | Как читается |
|---|---|---|
role="alert" | Требует немедленного внимания: ошибка, потеря данных | Скринридер прерывает текущее чтение |
role="status" | Всё остальное: подтверждение, справка | Дочитывает текущее, затем сообщает |
| без роли | Сообщение было на странице с самого начала | Читается в свою очередь, как обычный текст |
Модуль ролей не подставляет. role="alert" на статичном
сообщении, которое лежит на странице с загрузки, заставляет скринридер
прерваться на пустом месте; role="status" на блоке, который
так и не появится динамически, не делает ничего. Выбор зависит от того,
когда сообщение возникает, — а это знает разметка, а не CSS.
Иконка
Своих иконок библиотека не везёт — слот принимает что угодно: символ,
эмодзи или <svg>. Размер и выравнивание задаёт слот,
цвет наследуется от статуса.
Иконке нужен aria-hidden="true": она дублирует слово
заголовка, и без атрибута скринридер прочитает статус дважды — сначала
как «галочка», потом как «Готово».
Закрытие
.gr-alert-dismissible оставляет место под кнопку закрытия
в конце строки. Сама кнопка — обычная .gr-btn-icon.gr-btn-ghost,
а закрывает её делегированный обработчик [data-gr-dismiss]
из griffincss-ui.js. Крестик в примере ниже — рабочий:
скрипт подключён на этой странице.
Без скрипта кнопку закрытия просто не ставят. Мёртвый крестик, который не убирает сообщение, хуже отсутствия крестика: человек нажимает на него и получает подтверждение, что интерфейс сломан.
Полоса выполнения
| Класс | Назначение |
|---|---|
.gr-progress | Полоса на <progress>; значение показывает сам элемент |
.gr-progress-sm / -lg | Тоньше и толще |
.gr-progress-{success,warning,danger} | Цвет заполнения |
.gr-progress-indeterminate | Работа идёт, доля неизвестна: бегущий отрезок |
.gr-meter | Шкала на <meter>; цвет вычисляет браузер |
<label>
Загрузка файла — 40 %
<progress class="gr-progress" value="40" max="100">40 %</progress>
</label>
<progress class="gr-progress gr-progress-indeterminate">Выполняется</progress>
Вендорные псевдоэлементы объявлены каждый своим правилом.
Дорожку и заполнение рисуют ::-webkit-progress-bar,
::-webkit-progress-value и ::-moz-progress-bar,
и собрать их в один селектор через запятую нельзя: неизвестный селектор
делает невалидной всю группу, и WebKit, встретив рядом чужой
псевдоэлемент, выбрасывает правило целиком. Полоса осталась бы
некрашеной в обоих браузерах сразу.
Текст внутри <progress> — не подпись.
Это запасной путь для движка, который элемента не знает вовсе. Видимую
подпись ставят рядом — в <label>, как в примерах выше:
полоса без числа рядом сообщает «что-то происходит», но не сообщает,
сколько осталось.
Шкала
У шкалы нет классов-статусов, и это решение, а не пропуск.
<meter> сам вычисляет по low,
high и optimum, попало ли значение в хороший
диапазон, — модуль лишь красит три вычисленных состояния. Класс
.gr-meter-danger позволил бы разметке сказать «плохо» там,
где элемент вычислил «хорошо», и цвет разошёлся бы со смыслом.
<progress> — про ход задачи: он начинается
и заканчивается. <meter> — про измеренную
величину: заполненность диска, оценку, запас топлива. Полоса,
показывающая занятое место, — частая ошибка: у занятого места нет
завершения.
Рейтинг
| Класс или переменная | Назначение |
|---|---|
.gr-rating | Пять знаков, залитых по оценке. Отображение, а не ввод |
--gr-rating | Оценка числом от 0 до 5 на самом элементе; дробная рисуется частью знака |
--gr-rating-symbol | Знак; по умолчанию звезда, записанная экранированием "\2605". Токен ядра — свой глиф подставляется один раз на весь сайт |
--gr-color-rating | Цвет залитого знака — семантический токен темы: «цвет звёзд и всего, что изображает оценку». По умолчанию поверхностный цвет предупреждения — янтарь, читаемый в обеих темах; тема меняет его один раз, и следом перекрашиваются ряд, ввод оценки и всё, что вы покрасили тем же токеном |
--gr-rating-color | Переопределение на одном ряду: style="--gr-rating-color: var(--gr-color-danger)". По умолчанию не задана — ряд берёт --gr-color-rating |
Почему цвет оценки — токен темы, а не переменная модуля.
Текстовый --gr-color-warning затемнён ради контраста
на белом, и всё «предупреждающее» рядом со звёздами — полосы
распределения оценок, значок у подписи, ввод оценки — выходило
коричневым рядом с янтарными звёздами: два жёлтых в одной строке.
--gr-color-rating объявлен в теме на каждом её носителе,
как остальные --gr-color-*, поэтому его можно красить
в полосу распределения через style="color: var(--gr-color-rating)"
или в свой класс, и вложенная секция с другой темой перекрасит
его вместе с собой.
<span class="gr-rating" style="--gr-rating: 4.5" role="img" aria-label="4,5 из 5"></span>
<span class="gr-text-sm gr-text-ink-secondary">4,5 — 128 отзывов</span>
Оценка обязана быть и словом. Пять знаков — это цвет
и форма, то есть ровно то, что не переживает ни печать, ни программу
чтения с экрана. Поэтому элемент несёт role="img"
и aria-label вида «4,5 из 5», а рядом в примерах стоит
видимое число. Правило то же, что у сообщения о статусе: статус читается
словом, а не только цветом.
<!-- свой знак и свой цвет — переопределением токенов, не правилами -->
<span class="gr-rating gr-text-2xl"
style="--gr-rating: 4; --gr-rating-symbol: '♥'; --gr-rating-color: var(--gr-color-danger)"
role="img" aria-label="4 из 5"></span>
Размера у рейтинга нет и не нужно. Знаки — текст, поэтому
он растёт от кегля: .gr-text-2xl на самом элементе или
на строке вокруг. Пара классов .gr-rating-sm /
-lg была бы третьим способом сказать то, что уже говорят
утилиты типографики.
Знак приходит переменной, а не картинкой. Своего
иконочного набора библиотека не везёт — вместо него она держит слот
и контракт к нему («Кнопки и ссылки»),
а --gr-rating-symbol оставляет выбор глифа за проектом:
звезда, сердце, точка или знак из вашего иконочного шрифта. Ровно поэтому нельзя задать знакам
разрядку: интервал сдвигал бы границу заливки на целое число промежутков,
и половина пятого знака перестала бы приходиться на 90 % ширины. Просвет
рисует сам глиф.
Модуль показывает оценку, но не принимает её. Ввод — это
пять контролов, наведение, обход с клавиатуры и текущее значение формы:
поведение, а не оформление. Собирается оно из .gr-radio
в <fieldset> и остаётся за потребителем; притворяться,
что модуль умеет ввод, значило бы выдать картинку за поле.
Кольцо занятости
| Класс | Назначение |
|---|---|
.gr-spinner | Кольцо; диаметр — 1.5em от кегля родителя |
.gr-spinner-sm / -lg | Меньше и крупнее |
.gr-spinner-current | Кольцо цветом текста — для цветной кнопки |
<span class="gr-spinner" role="status" aria-label="Загрузка"></span>
<button class="gr-btn gr-btn-primary" disabled>
<span class="gr-spinner gr-spinner-sm gr-spinner-current" aria-hidden="true"></span>
Сохраняем…
</button>
Пустой <span> молчит. Кольцу нужны
role="status" и подпись, иначе человек, который его не видит,
не узнает, что страница чего-то ждёт. Если рядом уже стоит видимое слово —
как «Сохраняем…» в кнопке, — кольцу хватает
aria-hidden="true": иначе загрузка будет объявлена дважды.
При prefers-reduced-motion кольцо замедляется, но
не останавливается. Остановленный индикатор занятости сообщает
«зависло» — ровно обратное тому, зачем он поставлен. Мерцание заглушки
в такой же ситуации снимается совсем: она сообщает о загрузке самим
своим присутствием.
Всплывающие уведомления
| Класс | Назначение |
|---|---|
.gr-toast-region | Живая область; в разметке ей задают aria-live и роль |
.gr-toast-region-{top,bottom}-{start,end} | Четыре угла; у нижних колонка перевёрнута |
.gr-toast | Сообщение |
.gr-toast-{info,success,warning,danger} | Статус: полоса и цвет заголовка |
.gr-toast-title | Слово статуса |
.gr-toast-icon / -body | Слот под знак и текст |
.gr-toast-leaving | Уход; класс вешает рантайм |
Griffincss.ui.toast()Griffincss.ui.toast('Черновик сохранён', {
status: 'success', // info | success | warning | danger
title: 'Готово', // слово статуса — цвет один его не сообщает
timeout: 5000, // 0 — без таймера, только крестик
position: 'bottom-end'
});
| Параметр | По умолчанию | Что делает |
|---|---|---|
status | нет | Полоса и цвет заголовка; danger заодно переводит область в assertive |
title | нет | Слово статуса. Задан статус без слова — рантайм предупреждает в консоль |
timeout | 5000 | Миллисекунды до ухода; 0 — ждать крестика |
position | bottom-end | Один из четырёх углов |
icon | нет | Знак в слот .gr-toast-icon |
dismissible | true | false убирает крестик |
assertive | по статусу | Прямое указание, какой областью показать |
Живая область обязана существовать до вставки. Программа
чтения с экрана следит за изменениями внутри aria-live,
а не за появлением самой области, поэтому регион ставят на страницу
один раз и пустым:
<div class="gr-toast-region gr-toast-region-bottom-end"
role="status" aria-live="polite"></div>
Рантайм найдёт готовый регион и положит тост в него. Если региона нет, он создаст свой — это работает в современных движках, но остаётся запасным путём, а не рекомендацией.
У ошибки своя область. aria-live="polite"
дочитает текущее и только потом сообщит; потеря данных ждать не может,
поэтому status: 'danger' уходит в отдельный регион
с aria-live="assertive" и role="alert".
Таймер стоит под указателем и под фокусом. Сообщение, исчезающее ровно тогда, когда его начали читать или потянулись к ссылке внутри, — не удобство, а ловушка. Остаток дочитывается, а не начинается заново: иначе тост висел бы, пока по нему возят мышью.
Это единственный компонент библиотеки без статического
применения. Без griffincss-ui.js модуль
не используется вовсе — показать и убрать тост некому. Всё остальное
в библиотеке работает и без скрипта.
Появление объявлено начальным состоянием
Тост вставляют в документ уже нарисованным в конечном виде, поэтому
обычный переход ничего бы не показал: анимировать нечего, начального
кадра нет. Его задаёт @starting-style:
@starting-style {
.gr-toast {
opacity: 0;
translate: 0 var(--gr-toast-slide, -0.75rem);
}
}
Так рантайму не нужны ни второй кадр, ни пара «добавил класс — снял класс»: он просто кладёт узел в регион. Направление въезда берётся у региона — у верхних углов тост приезжает сверху, у нижних снизу.
Примеры использования
У обратной связи два вопроса: «каким компонентом сказать» и «на сколько оставить». Два рецепта ниже отвечают на оба: выбор между алертом и тостом и живая полоса загрузки с отменой.
Алерт или тост: правило выбора
Ошибка валидации остаётся алертом в потоке страницы: к ней возвращаются взглядом, пока правят поля, и она стоит рядом с причиной. Подтверждение успеха уходит тостом: перечитывать его незачем, а страница уже показывает результат. Обратное — тост об ошибке формы или вечный алерт «Сохранено» — заставляет либо запоминать исчезнувшее, либо закрывать очевидное. Кнопка в демо рабочая.
<!-- осталась на странице: к ней вернутся -->
<div class="gr-alert gr-alert-danger" role="alert">
<div class="gr-alert-title">Форма не отправлена</div>
<div class="gr-alert-body">Заполните почту и телефон.</div>
</div>
// мимолётное подтверждение
Griffincss.ui.toast('Черновик сохранён', { status: 'success', title: 'Готово' });
Загрузка файла: полоса, процент, отмена
Строка загрузки — медиа-объект: имя файла с .gr-truncate
(длинные имена не распирают строку), процент .gr-text-ink-secondary
напротив через .gr-flex-between, под ними —
<progress class="gr-progress"> на всю ширину и рядом
кнопка-крестик. Пока доля неизвестна, полоса живёт
в .gr-progress-indeterminate; проценты появляются вместе
с первыми байтами.
<div class="gr-flex gr-flex-col gr-gap-sm" style="max-width: 26rem">
<div class="gr-flex gr-flex-between gr-gap-sm gr-text-sm">
<span class="gr-truncate">annual-report-2026-final-v3.pdf</span>
<span class="gr-text-ink-secondary">64 %</span>
</div>
<div class="gr-flex gr-flex-items-center gr-gap-sm">
<progress class="gr-progress gr-flex-1" value="64" max="100">64 %</progress>
<button class="gr-btn gr-btn-icon gr-btn-ghost gr-btn-sm">✕<span class="gr-sr-only">Отменить загрузку</span></button>
</div>
</div>
.gr-truncate, полоса — .gr-flex-1, отмена — кнопка-иконка