Async content state
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Async content state

A standard way to handle loading, content, empty, error, offline and retry.

Draft

Specified, not implemented yet. It relies on Skeleton, Empty and a data library such as TanStack Query. The API may change.

Almost every screen that loads data goes through the same states. This pattern names them once, so each screen doesn’t reinvent the switch.

Orders
Offline · showing saved orders
Desk lamp, 2 bulbsSep 12#1042 · Delivered
Running shoesSep 9#1039 · Shipped
Notebook setAug 28#1031 · Delivered
Coffee beans, 1 kgAug 20#1027 · Delivered
Phone caseAug 2#1019 · Returned
HeadphonesJul 18#1008 · Delivered
No orders yetWhat you buy shows up here, with its tracking.Browse the shop
Couldn't load your ordersSomething went wrong on our side.
You're offlineYour orders load as soon as you're back online.
Couldn't refresh
Server returnsNetwork
state loading · first load

The screen loads on its own. Use the footer to choose what the server returns next, then tap Reopen screen. Pick Error and tap Try again: the button locks while the request runs. Switch Network to Offline and reopen, then back to Online: the screen retries without a tap. Now load the orders, set Error, and tap the refresh icon. The orders stay, and a snackbar reports the failure.

The five states side by side:

Orders
loading

Skeleton rows shaped like the content.

Orders
content

The data. A refresh keeps it visible.

Orders
No orders yetBrowse the shop
empty

Success with nothing in it. One way forward.

Orders
Couldn't loadTry again
error

Plain words, no code, a retry.

Orders
You're offlineTry again
offline

The fix is the network. Retries by itself.

How the states connect. Tap a transition to follow it, or a state to jump to it:

faileddatano datano connectionretryretry · back onlinerefreshloadingerrorcontentemptyoffline

A screen is in exactly one state at a time. Content already on screen stays visible during a refresh. The states below describe what replaces the content, not what covers it.

Loading

The first load, before any content exists.

  • Use a Skeleton shaped like the content when the layout is known, such as a list or a card grid.
  • Use a Spinner when the layout isn’t known, or for short, blocking actions.
  • A refresh of existing content is not a loading state. Use pull-to-refresh and keep the content on screen.

Empty

The request succeeded but returned nothing. There are two different cases, and they need different messages.

First use

The user hasn’t created anything yet. This is a chance to explain what the screen is for and to point to the main action, such as creating the first item.

  • A title that names what will live here.
  • One sentence on why it’s useful.
  • One primary action.

No results

The user searched or filtered, and nothing matched. The screen isn’t empty in general, only for this query.

  • Repeat the query or the active filters.
  • Offer a way out: clear the search, reset filters.
  • No primary “create” action: the user was looking for something, not trying to add it.

Both cases are built with Empty.

Error

The request failed.

  • Say what couldn’t be loaded, in plain language. No error codes on screen.
  • Offer a retry action.
  • If content was already on screen, keep it and report the error with a Snackbar or a Toast instead of replacing the screen.

Offline

The device has no connection. It is a specific error: the fix is on the user’s side, not the app’s.

  • Say that the device is offline, not that something went wrong.
  • Retry automatically when the connection comes back, and keep the manual retry.
  • Show cached content when there is some, with a discreet offline indicator.

Retry

Retry is a transition, not a screen: it sends the user back to loading.

  • The retry action goes back to the loading state, never straight to content.
  • Disable the action while the request is in flight, so repeated taps don’t stack requests.
  • After several failures in a row, stop retrying automatically and leave the manual action.