Обратная связь
Баннер
Сообщение уровня страницы: оно относится ко всему экрану, а не к тому, рядом с чем стоит. Поэтому у баннера есть заливка, рамка и место под действие.
Разметка
<div class="inst-banner" data-tone="warn">
<div class="inst-banner-body">
<div class="inst-banner-title">Доступ истекает через 3 дня</div>
<div class="inst-banner-text">После 8 августа запуск прогонов остановится.</div>
</div>
<div class="inst-banner-actions">
<button class="inst-btn inst-btn--sm" type="button">Продлить</button>
</div>
</div>
Контракт
| Что | Обязательно | Почему |
|---|---|---|
inst-banner-body вокруг текста |
да | min-inline-size: 0 — иначе длинное слово распирает баннер |
aria-live="polite" или role="alert" |
да, если баннер появился в ответ на действие | Роль не ставится автоматически: библиотека не знает, появился баннер сейчас или был изначально |
aria-label у кнопки закрытия |
да, если баннер закрывается | Крестик без имени не называет, что закрывает |
inst-banner-text |
нет | Подробность нужна не всегда: заголовок может остаться единственным |
Доступность
| Появление | Баннер, возникший в ответ на действие, обязан попасть в aria-live="polite", а сообщение об отказе — в role="alert". Роль не ставится автоматически: библиотека не знает, появился баннер сейчас или был на странице изначально |
| Цвет не единственный носитель | Тон несёт значок и текст. Иконка появилась именно потому, что warn и error раньше отличались только цветом |
| Контраст | Заголовок берёт --text-primary, подробность --text-secondary — оба на 4.5:1 против --tone-bg в пяти темах |
| Закрытие | Библиотеки оно не касается. Если баннер закрывается, кнопка закрытия — обычная иконочная кнопка с aria-label |
| Печать | Баннер печатается и не рвётся между листами: break-inside: avoid. Сообщение уровня страницы должно попасть в отчёт |
Устройство
| Класс | Работа |
|---|---|
inst-banner |
Контейнер: заливка тона, рамка, значок |
inst-banner-body |
Текстовая часть. min-inline-size: 0 против распирания |
inst-banner-title |
Заголовок. Может быть единственным содержимым |
inst-banner-text |
Подробность одной фразой |
inst-banner-actions |
Действия у дальнего края. Не сжимаются, переносятся при нехватке места |
Действия прижимаются к дальнему краю и не сжимаются, поэтому длинный текст переносится, а кнопка остаётся целой.
Значок центрируется по первой строке текста: calc((1lh - var(--size-icon)) / 2). Формула сама подстраивается под кегль и плотность, поэтому баннер с
одной строкой и баннер с тремя выглядят одинаково выровненными.
Варианты
Разметка
<div class="inst-banner" data-tone="warn">
<div class="inst-banner-body">
<div class="inst-banner-title">Доступ истекает через 3 дня</div>
<div class="inst-banner-text">После 8 августа запуск прогонов остановится.</div>
</div>
<div class="inst-banner-actions">
<button class="inst-btn inst-btn--sm" type="button">Продлить</button>
</div>
</div>
<div class="inst-banner" data-tone="error">
<div class="inst-banner-body">
<div class="inst-banner-title">Обслуживание хранилища</div>
<div class="inst-banner-text">Артефакты недоступны до 21:00.</div>
</div>
</div>
<div class="inst-banner" data-tone="ok">
<div class="inst-banner-body"><div class="inst-banner-title">Изменения сохранены</div></div>
</div>
Тон — тот же data-tone, что на бейдже и всюду ещё.
Он несёт иконку. Значок приходит из общего набора масок — одного на
баннер и сноску.
| Тон | Значок | Когда |
|---|---|---|
| без атрибута | нет | Нейтральное сообщение без значка |
data-tone="neutral" |
«i» в круге | Информация. Роль info исполняет neutral — синий в библиотеке занят акцентом |
data-tone="running" |
«i» в круге | Идёт прямо сейчас: обслуживание, миграция |
data-tone="ok" |
галка в круге | Свершилось успешно |
data-tone="warn" |
треугольник | Условие, которое скоро станет проблемой |
data-tone="error" |
крест в круге | Что-то не работает прямо сейчас |
Без data-tone значок не рисуется вовсе (content: none), и баннер остаётся
на нейтральной приглушённой поверхности.
JS
Подключите модуль один раз на страницу — инициализировать компоненты по
отдельности не нужно, instrument.js работает делегированием и видит узлы, пришедшие
позже.
<script type="module" src="instrument.js"></script>
Что делает instrument.js
Ничего. Крестика у баннера нет: баннер сообщает о состоянии системы, а состояние не исчезает оттого, что его закрыли. Закрытие имеет смысл только там, где приложение знает, что делать с закрытым.
Если закрытие всё-таки нужно
Кнопка — обычная, и её надо добавить самому. Вместе с ней придётся решить, что значит «закрыт»: до перезагрузки, до конца сессии или навсегда.
document.addEventListener('click', (e) => {
const id = e.target.closest('[data-close]')?.dataset.close;
if (!id) return;
document.getElementById(id).hidden = true;
sessionStorage.setItem('hidden:' + id, '1'); // до конца сессии, а не навсегда
});
hidden, а не удаление узла: баннер вернётся, когда состояние повторится, и
воссоздавать разметку заново незачем.
API
| Имя | Что делает |
|---|---|
| класс | |
inst-banner | Контейнер. Заливка тона, рамка, значок |
inst-banner-body | Текстовая часть. min-inline-size: 0, чтобы длинное слово не распирало баннер |
inst-banner-title | Заголовок, средняя насыщенность |
inst-banner-text | Подробность, --text-secondary |
inst-banner-actions | Действия у дальнего края. Не сжимаются, переносятся при нехватке места |
| Имя | Значение | Что делает |
|---|---|---|
| атрибут | ||
data-tone | — | neutral running ok warn error. Закрыт |
| Имя | Значение | Откуда |
|---|---|---|
| токен | ||
--tone-bg | var(--surface-sunken) | база +4: |
--tone-ink | var(--accent-text) | база +2: |
--border | light-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11)) | одно на все ячейки |
--hairline | 1px | база +1: экран |
--radius-md | 8px | база +4: масштаб |
--pad-panel | var(--space-5) | база +14: плотность · масштаб |
--space-3 | 6px | одно на все ячейки |
--space-4 | 8px | одно на все ячейки |
--gap-inline | var(--space-4) | база +14: плотность · масштаб |
--size-icon | 18px | база +4: масштаб |
--text-sm | 0.875rem | база +4: масштаб |
--text-secondary | light-dark(var(--n-9), var(--n-5)) | одно на все ячейки |
--weight-medium | 600 | одно на все ячейки |
--leading-ui | 1.4 | одно на все ячейки |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument