Состояния экрана: 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.