checklistuiuxstatesqa

Состояния экрана: empty, loading, error и то, что забывают, пока не прилетит баг

Почти любой экран рисуют для одного сценария — «всё хорошо, данные есть». Дизайн в Figma красивый, список заполнен, аватарки на месте. А пользователь первым делом видит совсем другое: загрузку, пустоту или ошибку. И вот эти состояния обычно никто не проектировал — они достаются как придётся.

Расскажу про состояния экрана, которые забывают, пока по ним не прилетит баг. С примерами, потому что почти на каждое я в своё время наступал.

Пустой экран, который выглядит как зависшая загрузка

Любимое. Открываешь раздел — белый экран. Это что: ещё грузится? уже загрузилось, но пусто? или отвалилось и молчит? Пользователь не знает, и большинство решают, что «сломалось», и уходят. А на деле всё работает — просто у экрана нет состояния «здесь пока пусто».

Отсюда правило, с которого я начинаю: у каждого экрана со списком/данными должно быть минимум четыре прорисованных состояния — загрузка, есть данные, пусто, ошибка. Если хоть одно «достаётся как получится» — там и будет баг.

Loading: не спиннер ради спиннера

Загрузка — это не «крутилка по центру и всё». Что я проверяю: не дёргается ли вёрстка, когда долетели данные (контент не должен прыгать и сдвигать кнопку под пальцем — это тот самый layout shift, см. CLS). Скелетоны обычно лучше спиннера, потому что показывают будущую структуру и ощущаются быстрее (NN/g про skeleton screens). И отдельно — что происходит, если загрузка зависла навсегда: есть ли таймаут и переход в ошибку, или крутилка вечная.

Две разные пустоты, которые путают

Это важно, и это правда путают. «Пусто» бывает двух сортов, и сообщения у них разные:

  • Первый вход / ещё ничего нет — у пользователя пустая корзина, ноль заказов, новый аккаунт. Здесь пустота — это нормально, и экран должен не извиняться, а подсказывать первый шаг: «Добавьте первый товар».
  • Ничего не найдено — был фильтр или поиск, и под него ничего не подошло. Тут нужно другое: сказать, что критерии слишком узкие, и дать сбросить фильтр. Совет «добавьте первый товар» здесь звучит издевательски.

Хороший разбор пустых состояний — у NN/g про empty states. Если коротко: пустой экран — это не «нет контента», это возможность сказать пользователю, что делать дальше.

«Нет данных» и «не смогли загрузить» — это НЕ одно и то же

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

Это разные состояния с разными действиями. Пусто → «добавьте/измените фильтр». Ошибка → честное «что-то пошло не так» и кнопка «Повторить», а не белый экран и не вечная крутилка. Хорошие сообщения об ошибке объясняют, что случилось и что делать, без «Error 500» в лицо (Smashing про error messages).

Состояния, до которых руки доходят в последнюю очередь

По моему опыту, ломается обычно тут:

  • Offline / нет сети — отдельно от ошибки сервера: «нет соединения, проверьте интернет», и желательно автоповтор, когда сеть вернулась.
  • Частичная загрузка — часть данных пришла, часть отвалилась. Показываем что есть и помечаем, что не загрузилось, а не роняем весь экран из-за одного блока.
  • Устаревшие данные (stale) — показываем кэш, пока грузим свежее, но честно помечаем, что это кэш, иначе пользователь примет решение по старым числам.
  • Ошибка на догрузке — список загрузился, а следующая страница при скролле упала: не должно затирать уже показанное, нужна возможность повторить именно догрузку.
  • Граничное: ровно один элемент, ровно ноль, очень длинный контент, который ломает верстку пустого/ошибочного состояния.

Пустое и ошибочное — это тоже доступность

Про это забывают совсем. Сообщение об ошибке, которое появилось динамически, скринридер должен объявить — для этого есть роль alert / aria-live (ARIA alert pattern). Ошибку нельзя обозначать только цветом (красная рамка без текста незрячему ничего не говорит), и фокус разумно вести на сообщение или на «Повторить». Пустое состояние тоже должно читаться, а не быть просто пустым контейнером.

Компактный чек-лист

  • У каждого экрана с данными есть 4 состояния: loading / есть данные / empty / error
  • Loading не дёргает вёрстку (нет layout shift), есть таймаут → ошибка, а не вечная крутилка
  • Empty различает «ещё ничего нет» и «ничего не найдено» — разные тексты и действия
  • Error ≠ empty: при сбое честное сообщение + «Повторить», не белый экран
  • Offline отделён от ошибки сервера; автоповтор при возврате сети
  • Частичная загрузка не роняет весь экран; устаревшие данные помечены
  • Ошибка на пагинации/догрузке не затирает уже показанное
  • Границы: 0 / 1 / очень много элементов, очень длинный текст
  • Сообщения об ошибке объявляются скринридеру (alert/aria-live), не только цветом
  • Повторный запрос реально срабатывает и убирает состояние ошибки

Если коротко

Happy path тестируют все. Деньги и нервы пользователей теряются на остальных состояниях — когда пусто выглядит как поломка, ошибка выглядит как пустота, а загрузка не кончается. Я для себя давно решил: экран не готов, пока я не посмотрел на него без сети, с пустым ответом и с пятисотым. Это пять минут, а ловит то, что иначе прилетит из прода.

Источники: NN/g — Empty states, Smashing — Error messages UX, NN/g — Skeleton screens, web.dev — Cumulative Layout Shift, W3C ARIA — Alert pattern.