Empty, loading and error states
Every list has three states besides the data: still loading, nothing here, and failed. Each says what is true and what to do, and none of them shows made-up data.
Loading
Nothing under a second. Skeleton rows from one to three seconds, sized like real rows so the list doesn't jump. Progress to ten seconds, then a background task.
Show code
<SkeletonRows variant="desk" count={3} />Empty
One serif line saying what is true, one line saying what to do, one action that resolves it. The tone depends on the cause: first use teaches, all clear celebrates, no results suggests clearing a filter.
No requests in this queue
New requests appear here as customers write in. Clear the filters to see everything.
Show code
<AdminEmptyState variant="visitors" title="No conversations yet" description="…" action={{ label: "Install the widget", href: "…" }} />Error
Say what happened and what to do, next to the thing that failed, with a retry. The load hook keeps foreground retry and background refresh apart, so a failed refresh doesn't wipe the list.
Couldn't load this queue. Check your connection and try again.
Show code
<AdminLoadError message={loadErrorCopy("this queue")} onRetry={retry} />Use useAdminLoad for every data load. Never show fallback or sample data as if it were real.
Show code
const load = useAdminLoad("conversations", fetchConversations, []);
if (load.status === "loading") return <SkeletonRows variant="conv" />;
if (load.status === "error") return <AdminLoadError message={loadErrorCopy("conversations")} onRetry={load.retry} reason={load.error} />;
if (load.data.length === 0) return <AdminEmptyState variant="visitors" action={…} />;Feature off
When the API answers 503 because an optional feature is switched off, say which feature and where to turn it on. Do not print the status code. Tell it apart from a real failure with isFeatureOffError(err).
This needs the bee mesh feature. Turn it on in Feature flags, then come back.
Show code
<SettingsFeatureOff feature="bee mesh" />When to use
- Load with useAdminLoad(label, fetcher, deps), then render skeleton, error, empty or data. Foreground is retry; background is refresh.
- If a value is unknown, print an en dash or leave the block out. No lorem ipsum, no fake counts.
- A breach or a failed automation is a persistent notice, never a timed toast.
- After a failed save, move focus to the first error.
Couldn't load this queue. Check your connection and try again.
Error 500
Keyboard and screen readers
- Skeleton rows are a status region with aria-busy and a label, so a screen reader hears Loading once.
- A load error is a ruby notice with role alert, announced when it appears; its retry is a real button.
- The empty state is a status region named by its title, with a visible action.
- Shimmer runs only when reduced motion is off.