Перейти к содержимому
Плотность
Тема

Обратная связь

Баннер

JS Закрытие баннера — слой приложения. Кит не даёт крестика намеренно

Сообщение уровня страницы: оно относится ко всему экрану, а не к тому, рядом с чем стоит. Поэтому у баннера есть заливка, рамка и место под действие.

Компонент
Доступ истекает через 3 дня
После 8 августа запуск прогонов остановится.
Разметка
<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="warn">
  <div class="inst-banner-body">
    <div class="inst-banner-title">Заголовок</div>
    <div class="inst-banner-text">Подробность одной фразой.</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 нет Подробность нужна не всегда: заголовок может остаться единственным

Когда использовать

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

Устройство

Класс Работа
inst-banner Контейнер: заливка тона, рамка, значок
inst-banner-body Текстовая часть. min-inline-size: 0 против распирания
inst-banner-title Заголовок. Может быть единственным содержимым
inst-banner-text Подробность одной фразой
inst-banner-actions Действия у дальнего края. Не сжимаются, переносятся при нехватке места

Действия прижимаются к дальнему краю и не сжимаются, поэтому длинный текст переносится, а кнопка остаётся целой.

Значок центрируется по первой строке текста: calc((1lh - var(--size-icon)) / 2). Формула сама подстраивается под кегль и плотность, поэтому баннер с одной строкой и баннер с тремя выглядят одинаково выровненными.

Варианты

Пример
Доступ истекает через 3 дня
После 8 августа запуск прогонов остановится.
Обслуживание хранилища
Артефакты недоступны до 21:00.
Изменения сохранены
Разметка
<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

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

<script type="module" src="src/kit.js"></script>

Что делает кит

Ничего, и это решение, а не пробел. Кит не рисует крестика: баннер сообщает о состоянии системы, а состояние не исчезает оттого, что его закрыли. Закрытие имеет смысл только там, где приложение знает, что делать с закрытым.

Если закрытие всё-таки нужно

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

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, а не удаление узла: баннер вернётся, когда состояние повторится, и воссоздавать разметку заново незачем.

Правила

Так Баннер про весь экран

«Идёт обслуживание» верно вне зависимости от того, куда смотрит человек. Такое сообщение держится, пока держится состояние.

Не так Баннер вместо сноски

Оговорка к соседнему полю не претендует на весь экран — это сноска: полоса у края, без заголовка и без действия.

Так Одно действие рядом с сообщением

Действие есть, вопроса нет. Если без ответа нельзя продолжить — нужна модалка.

Не так Баннер для отказа с повтором

У сбоя должны быть причина и то, что уже пробовали, — это блок отказа.

Доступность

Появление Баннер, возникший в ответ на действие, обязан попасть в aria-live="polite", а сообщение об отказе — в role="alert". Кит роли не ставит: он не знает, появился баннер сейчас или был на странице изначально
Цвет не единственный носитель Тон несёт значок и текст. Иконка появилась именно потому, что warn и error раньше отличались только цветом
Контраст Заголовок берёт --text-primary, подробность --text-secondary — оба на 4.5:1 против --tone-bg в шести темах
Закрытие Кита оно не касается. Если баннер закрывается, кнопка закрытия — обычная иконочная кнопка с aria-label
Печать Баннер печатается и не рвётся между листами: break-inside: avoid. Сообщение уровня страницы должно попасть в отчёт

API

ИмяЗначениеЧто делает
класс
inst-bannerКонтейнер. Заливка тона, рамка, значок
inst-banner-bodyТекстовая часть. min-inline-size: 0, чтобы длинное слово не распирало баннер
inst-banner-titleЗаголовок, средняя насыщенность
inst-banner-textПодробность, --text-secondary
inst-banner-actionsДействия у дальнего края. Не сжимаются, переносятся при нехватке места
атрибут
data-toneneutral running ok warn error. Закрыт
токен
--tone-bgvar(--surface-sunken)
--tone-inkvar(--accent-text)
--borderlight-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11))
--hairline1px
--radius-md7px
--pad-panelvar(--space-5)
--space-36px
--space-48px
--gap-inlinevar(--space-4)
--size-icon16px
--text-sm0.8125rem
--text-secondarylight-dark(var(--n-9), var(--n-5))
--weight-medium500
--leading-ui1.4

Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument