Сообщения

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

griffincss-ui packages/ui/scss/_alert.scss packages/ui/scss/_progress.scss packages/ui/scss/_rating.scss packages/ui/scss/_spinner.scss packages/ui/scss/_toast.scss

Сообщение о статусе

КлассНазначение
.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.
Готово Все 120 тестов пройдены, check-dist без замечаний.
Внимание Пакет не опубликован в npm — установка идёт из репозитория.
<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>
Целая оценка, дробная и пустая
5,0 — 128 отзывов
4,5 — 64 отзыва
3,2 — 9 отзывов
Оценок пока нет

Оценка обязана быть и словом. Пять знаков — это цвет и форма, то есть ровно то, что не переживает ни печать, ни программу чтения с экрана. Поэтому элемент несёт 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нетСлово статуса. Задан статус без слова — рантайм предупреждает в консоль
timeout5000Миллисекунды до ухода; 0 — ждать крестика
positionbottom-endОдин из четырёх углов
iconнетЗнак в слот .gr-toast-icon
dismissibletruefalse убирает крестик
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, отмена — кнопка-иконка
annual-report-2026-final-v3-really-final.pdf 64 %
64 %
фото-со-встречи.zip ожидание…
Загружается