Перейти к содержимому
EN
Масштаб
Плотность
Тема
Акцент

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

Уведомление

JS Очередь, таймер и пауза под курсором — на instrument.js. Вид, появление и уход — целиком CSS

Результат действия, которое запустил человек, — когда результату не место на экране, куда он смотрит. «Прогон поставлен в очередь», «настройки сохранены», «не удалось отправить».

Компонент
Прогон поставлен в очередь
worldbox-1 · седьмой в очереди
Настройки сохранены
Разметка
<div class="inst-toasts" style="position:static;inset:auto;pointer-events:auto">
  <div class="inst-toast" data-tone="ok" role="status">
    <div class="inst-toast-body">
      <div class="inst-toast-title">Прогон поставлен в очередь</div>
      <div class="inst-toast-text">worldbox-1 · седьмой в очереди</div>
    </div>
    <div class="inst-toast-actions">
      <button class="inst-btn inst-btn--sm inst-btn--ghost" type="button">Показать</button>
    </div>
  </div>

  <div class="inst-toast" data-tone="error" role="alert">
    <div class="inst-toast-body">
      <div class="inst-toast-title">Не удалось отправить</div>
      <div class="inst-toast-text">Сеть недоступна. Попытка 3 из 5.</div>
    </div>
  </div>

  <div class="inst-toast">
    <div class="inst-toast-body">
      <div class="inst-toast-title">Настройки сохранены</div>
    </div>
  </div>
</div>

Инлайновый стиль в примере — единственный на весь справочник, и он тут вынужденный: настоящая область живёт в верхнем слое и прижата к углу экрана, а показать её надо внутри кадра. В приложении так писать не нужно.

Контракт

Разметку строит instrument.js: тост заводится вызовом, а не написанным вручную узлом. Если стандартной не хватает, соберите свой и положите в .inst-toasts сами.

Что Обязательно Почему
role="status", у ошибки role="alert" да Ошибка перебивает, остальное сообщает вежливо
duration: 0 у ошибки да Сообщение о том, что действие не выполнено, не имеет права уйти незамеченным
Одно действие, не два да Тост уходит сам; выбор из двух вариантов требует времени, которого у него нет
Область одна на документ да popover="manual", открыта с инициализации

Доступность

Роль status у обычного, alert у ошибки. Ошибка перебивает, остальное сообщает вежливо
Живой регион Область существует и открыта до появления первого тоста. Регион, созданный одновременно с содержимым, не озвучивается
Пауза Таймер замирает под курсором и на фокусе — WCAG 2.2.1
Ошибка duration: 0: сообщение о невыполненном действии не уходит само
Не только цвет Тон несёт значок, а не одну заливку — тот же набор масок, что у сноски и баннера
Уменьшенное движение Переход схлопывается; удаление подстраховано таймером, иначе тост остался бы навсегда
Клавиатура Действие внутри — обычная кнопка и достижимо Tab. Область не перехватывает фокус: она сообщает, а не спрашивает
Печать Уведомления на листе не печатаются: они по определению временные
Модалка Тост под ней не виден. Сообщайте результат внутри диалога или после закрытия — см. выше

Поведение

Почему popover

Область объявлена popover, потому что z-index не решает задачу целиком. Тост обязан быть виден поверх всего, что нарисовало приложение: панели с overflow: hidden, липкой шапки, открытого поповера. Верхний слой даёт это по построению, а число в z-index — только до первого чужого числа побольше.

Отсюда же в библиотеке нет отдельного z-индекса для тостов: применить его было бы некуда.

Модалка выше тоста

Тост не перекрывает открытую модалку. Проверено замером: область, переоткрытая после showModal() — и в той же задаче, и через задачу, — остаётся под диалогом. Верхний слой упорядочен по времени входа, но модальный диалог держится выше манипуляций с popover.

Что делать вместо:

Ситуация Как
Действие запущено из модалки и она закрывается Закройте диалог, потом покажите тост
Модалка остаётся открытой Результат — внутри неё: сноска или блок отказа в теле диалога
Долгая операция при открытой модалке Мера внутри диалога, а тост — после закрытия

Так даже правильнее: сообщение о результате действия, запущенного в диалоге, и должно приходить туда, куда смотрит человек.

Почему область открыта всегда

popover="manual" открывается один раз при инициализации и не закрывается.

Закрытый popover — это display: none, а живой регион в display: none не озвучивается: скринридер не сообщил бы ни одного уведомления. Пустая открытая область невидима и ничего не перехватывает — pointer-events возвращаются только самим тостам.

Пауза и потолок очереди

Таймер замирает под курсором и на фокусе. Уведомление, исчезнувшее тогда, когда его начали читать, — это потерянное сообщение, и WCAG 2.2.1 требует этого: у времени должна быть пауза.

Больше четырёх одновременно не показывается — двадцать уведомлений подряд читаются как стена. Самые старые уходят, чтобы новое было видно.

JS

Тост не пишут разметкой — его зовут. Поэтому пример здесь один и он живой: нажмите, и уведомление придёт в правый нижний угол экрана.

Пример
Разметка
<button class="inst-btn inst-btn--primary" type="button"
        data-demo-toast='{"tone":"ok","title":"Прогон поставлен в очередь","text":"worldbox-1 · седьмой в очереди"}'>Успех</button>
<button class="inst-btn" type="button"
        data-demo-toast='{"tone":"error","title":"Не удалось отправить","duration":0}'>Ошибка, которая не уходит</button>
<button class="inst-btn" type="button"
        data-demo-toast='{"tone":"running","title":"Идёт сборка","text":"Осталось два шага"}'>Идёт</button>

data-demo-toast — атрибут этого сайта, а не библиотеки: он нужен, чтобы страница осталась разметкой. В приложении вы вызываете toast() из кода.

import { toast } from '@keshon/instrument/js';

toast({ tone: 'ok', title: 'Прогон поставлен в очередь',
        text: 'worldbox-1 · седьмой в очереди' });

Без скрипта тостов нет вовсе: очередь, таймер и пауза под курсором — работа, которую CSS не выполняет. Вид, появление и уход при этом целиком на CSS.

Методы

toast(options) Показать уведомление. Возвращает узел — его можно убрать раньше срока

Опции

Поле Что делает
title Что произошло. Одна строка, без точки
text Подробность. Необязательна
tone ok · warn · error · running · neutral
duration Миллисекунды. 0 — не уходит сам
action { label, onClick } — одно действие, не два
toast({ tone: 'error', title: 'Не удалось отправить', duration: 0 });

toast({ title: 'Задача удалена',
        action: { label: 'Вернуть', onClick: () => restore() } });

События

Своих нет. Действие приходит колбэком action.onClick.

API

Классы
ИмяЧто делает
класс
inst-toastsОбласть. Одна на документ, объявлена popover="manual"
inst-toastОдно уведомление
inst-toast-bodyТекстовая часть
inst-toast-titleЧто произошло. Одна строка
inst-toast-textПодробность. Необязательна
inst-toast-actionsДействие: «Отменить», «Показать»
модификатор
inst-toasts--startОбласть у верхнего края. Умолчание — нижний
Атрибуты
ИмяЗначениеЧто делает
атрибут
data-toneneutral · running · ok · warn · errorЗначок и цвет. Без атрибута значка нет
data-stateleavingСтавит instrument.js перед удалением, чтобы переход доиграл
rolestatus · alertalert только у ошибки: она перебивает, остальное сообщает вежливо
popovermanualНа области. Верхний слой, без лёгкого закрытия
Токены
ИмяЗначениеОткуда
токен
--surface-overlayПоверхность уведомленияlight-dark(var(--n-0), var(--n-11))база +1: тема
--shadow-popoverТретий и последний носитель тени в библиотеке0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near)одно на все ячейки

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