States
Empty, Error, Loading, Success, 404, Permission, Offline. Восемь состояний, в которых интерфейс не показывает контент. Это не edge cases — это до 30% всех экранов в продукте. Их нужно проектировать так же тщательно, как happy path.
Empty
Нет данных, нет результатов, пустой инбокс. Тёплый тон: объясняем почему пусто и даём один первый шаг. Не «0 records found» — а «Здесь будут ваши запросы. Создайте первый.»
Empty — первое использование
Здесь будут ваши запросы
Создайте первый запрос — спросите что-то у ваших данных, и Analyst найдёт ответ.
Empty — нет результатов поиска
Ничего не нашлось по «отказы Q4»
Попробуйте упростить запрос или сбросить фильтры — возможно, период слишком узкий.
Анатомия Empty-состояния
| 1. Иконка | Простая lucide icon в квадрате secondary — контекст («это инбокс», «это поиск»). 56×56. |
| 2. Заголовок | Что здесь будет, не что его нет. «Здесь будут запросы», не «Нет запросов». |
| 3. Объяснение | 1–2 предложения. Почему пусто и что нужно сделать. |
| 4. Action | Один primary CTA + опционально secondary («посмотреть пример», «прочитать гайд»). |
Error — три уровня
Ошибка может проявиться на трёх уровнях. Уровень определяет место и вес сообщения.
Inline — под полем
Когда ошибка относится к конкретному полю формы. Не дёргает остальной экран.
Page-level — вместо контента
Когда не удалось загрузить страницу или её ключевую часть. Контент заменяется состоянием.
Не удалось загрузить список
Что-то пошло не так на нашей стороне. Мы уже знаем и разбираемся.
request id: 7f2e1a · 12:14:33
Toast — фоновое действие
Когда что-то сломалось при фоновом действии (autosave, синхронизация, отправка), но сам экран жив. Toast в нижнем правом, dismissable.
Уровни — когда какой
| Контекст | Уровень | Где |
|---|---|---|
| Поле формы | Inline | Под полем, красный текст + бордер. |
| Страница не загрузилась | Page-level | Вместо контента, full-bleed. |
| Action в модалке/sheet | Inline | Сверху модалки, banner-стиль. |
| Фоновое действие | Toast | Bottom-right, auto-dismiss 8s или persistent. |
| Action юзера упал | Toast | Например удаление, sharing, отправка email. |
Action — контекстный
| Тип ошибки | Action |
|---|---|
| Сетевая (timeout, 5xx) | «Попробовать ещё раз» — retry того же запроса. |
| Валидация (4xx от юзера) | Объяснение что не так. Action — исправить, не retry. |
| Permission | «Запросить доступ» или «Сменить аккаунт». Не retry. |
| Не найдено (404) | «Вернуться» или «К списку». Retry бессмысленен. |
| Серверная критическая | «Попробовать» + «Сообщить нам» с request id. |
Loading
Skeleton для страниц и контентных блоков, spinner для кнопок и мелких операций. Skeleton показывает структуру будущего контента, spinner — просто «работаю».
Skeleton — страница, список
Контуры повторяют структуру финального контента. Shimmer-анимация даёт обратную связь «загружается».
Spinner — в кнопке
Когда юзер инициировал короткое действие — сохранение, отправка. Кнопка disabled, текст меняется на spinner + продолжение.
Когда skeleton, когда spinner
| Загрузка страницы целиком | Skeleton всех ключевых блоков. |
| Список / таблица | Skeleton 4–6 строк. |
| Один блок виджета | Skeleton с нужной формой. |
| Кнопка / inline-action | Spinner внутри кнопки + disabled. |
| Микро-операция (<200ms) | Не показываем ничего — моргание раздражает. |
| Долгая загрузка (>3s) | Skeleton + поясняющий текст. См. ниже. |
Skeleton-токены
| Цвет base | #EDEDF0 |
| Цвет highlight | #F5F5F7 |
| Анимация | shimmer 1.4s linear infinite |
| Высота строки текста | 12 px (small) / 14 px (body) |
| Border-radius | 6 px / round для аватарок |
Долгая загрузка (>3 секунд)
Если что-то грузится дольше 3 секунд — нужно объяснить. Молчаливый spinner на 5+ секунд читается как «зависло».
Собираем отчёт
Это занимает чуть больше времени — объём за квартал.
Тайминг сообщений
| Время | Что показываем |
|---|---|
| 0–200 ms | Ничего. Ответ должен прилететь сразу. |
| 200 ms – 3 s | Skeleton или spinner без текста. |
| 3 s – 10 s | Spinner + «Это занимает чуть больше времени …» |
| 10 s+ | Прогресс-бар или этапы («Собираем данные… Считаем метрики…»). |
| 30 s+ | Background job — отпустить юзера, прислать уведомление по завершении. |
Success
Когда action завершился. Чаще всего — toast в углу, не page-level. Page-level success — только для крупных событий (оплата, регистрация, экспорт большого файла).
Success — toast
Success — page-level
Подписка активна
План «Team» — до 15 апреля 2026. Чек отправили на ivan@acme.ru.
404 — страница не найдена
Юзер прошёл по битой ссылке или удалённому объекту. Без «Sorry», без шуток про 404. Спокойно, по делу.
404
Этой страницы нет — возможно, её переименовали или удалили.
Permission denied
Объект существует, но у юзера нет прав. Не путать с 404. Объясняем почему нет доступа и даём путь его получить.
Только для администраторов
Биллинг доступен пользователям с ролью «admin». Попросите Ивана Петрова дать доступ — он владелец организации.
Offline / нет сети
Сеть пропала. Не генерируем error page — показываем баннер сверху, держим интерфейс работоспособным насколько возможно. Возвращаем функциональность сразу, как сеть вернётся.
Что делаем при offline
| Просмотр того что уже загружено | Работает. Не блокируем. |
| Действия меняющие данные | Кэшируем локально, синкаем при возврате связи. |
| Реал-тайм-фичи (чат, графики) | Замораживаем, помечаем «не актуально». |
| При возврате связи | Banner становится «Соединение восстановлено» green, потом исчезает через 3s. |
Голос — тон сообщений
Человечный, без технических терминов и без извинений-преувеличений. Ответственность берём, не перекладываем на юзера.
| Не так | Так |
|---|---|
| Error 500: Internal Server Error | Что-то пошло не так на нашей стороне. Мы уже знаем. |
| Sorry! Something went wrong :( | Не удалось загрузить список. Попробуем ещё раз? |
| Failed to validate. Email is invalid. | Похоже, домен неполный. Попробуйте ivan@aist.tech. |
| No data | Здесь будут ваши запросы. Создайте первый. |
| You don't have permission | Только для администраторов. Попросите Ивана дать доступ. |
| Unable to connect to server | Нет связи с сервером. Изменения сохраняются локально. |
Правила голоса
- Берём ответственность. «На нашей стороне», не «у вас что-то не так».
- Объясняем причину кратко. «Объём за квартал», «домен неполный» — одной фразой.
- Без извинений-каскадов. Одного «не удалось» хватит. «Sorry, our apologies, we deeply regret» — нет.
- Без «!» и капса. Спокойный тон. Восклицания только в success.
- Действие в первом лице. «Попробуем ещё раз», «Сообщим вам» — а не «Try again later».
- Никогда не вините юзера. Не «вы ввели неправильно» — а «похоже, опечатка».
Принципы
Empty показывает что будет и даёт первый шаг.
Empty — просто текст «No data» в центре экрана.
Skeleton повторяет финальный layout.
Один большой spinner на всю страницу.
Error содержит request-id для поддержки.
«Something went wrong» — и всё.
Долгая загрузка объясняет себя через 3 секунды.
Spinner крутится 30 секунд молча.
Success — чаще всего toast, не page.
Полноэкранная success-страница после каждого save.
Tokens reference
| State | Property | Value |
|---|---|---|
| Empty icon | size / radius / bg | 56 × 14 / secondary |
| Error page icon | size / radius / bg | 64 × 16 / destructive @8% |
| Toast | radius / shadow | 10 / 0 8 24 rgba(17,17,17,.06) |
| Toast width | max | 380 px |
| Skeleton | base / highlight | #EDEDF0 / #F5F5F7 |
| Skeleton | animation | shimmer 1.4s linear inf |
| Spinner | size / stroke | 16 × 2 / 24 × 2.5 |
| Spinner | duration | 0.7s linear inf |
| 404 numeral | size / weight | 64 / 800 |
| Offline banner | bg / dot | #1C1C1F / warning |
Назад: ← Density · К хабу: Design System →