Empty & Error States
What a screen shows when there's nothing to show, or when something failed. Learn the six types, what a good one says, how testers force them with fresh accounts, airplane mode and proxy tools, and the spinner, flash and Retry bugs they find.
Designers spend weeks on the screen full of beautiful data. Then a real user signs up, opens Orders for the first time and sees… a white screen. Or a spinner that never stops. Or Error 500. The “nothing here” and “something broke” screens are often designed last, built in a hurry, and tested by nobody.
That makes them a gift for testers. Every list, feed, search and dashboard in an app has these states, and you can trigger most of them in a minute if you know how.
What are empty and error states?
An empty stateEmpty stateThe screen a user sees when there is no content yet, like an empty cart or no search results.Open in glossary → is what a screen shows when it has no content to display. An error state is what it shows when it couldn’t get or show the content. Together with the loading stateLoading stateWhat the UI shows while it waits for data or an action to finish, such as a spinner or skeleton.Open in glossary → and the normal data state, they make up the UI statesUI stateHow a component looks and behaves in a given situation: default, hover, pressed, focused, disabled, loading, error.Open in glossary → that every data screen must handle.
The key idea: “empty” and “failed” are different situations and need different messages. “You have no orders” and “We couldn’t load your orders” might both show an empty list, but they mean opposite things to the user.
The six types
Orders
No orders yet
When you order food, you'll find your orders and receipts here.
First use
A brand-new user, nothing created yet.
Create it: Sign up with a fresh test account.
Inbox
All caught up
You've read every message. New ones will appear here.
User cleared
The user finished or deleted everything.
Create it: Delete, archive or complete every item.
Search
No results for "sushi vegan gluten-free"
Check the spelling or remove a filter.
No results
A search or filter matches nothing.
Create it: Search for "zzqx" or combine filters that exclude everything.
Team orders
Only admins can see team orders
Ask an admin of Acme Ltd. to give you access.
No permission
Data exists, but this user may not see it.
Create it: Log in with a lower role, or deny an OS permission.
Orders
You're offline
Check your connection. We'll reload as soon as you're back.
Offline
The device has no connection.
Create it: Airplane mode, Wi-Fi off, or a proxy that blocks requests.
Orders
We couldn't load your orders
This is on our side. Please try again in a moment.
Error
The server or the app failed.
Create it: Map local / breakpoint a 500, a timeout or broken JSON.
Loading vs empty vs error
A screen that loads data goes through a few states. It should show exactly one at a time:
| State | What it means | What the user should see |
|---|---|---|
| Loading | Request on its way | Skeleton or spinner, ideally in the same layout as the content |
| Data | Request OK, items returned | The content |
| Empty | Request OK, zero items | An empty state with a next step |
| Error | Request failed (offline, timeout, 500, 403) | An error state with a way to recover |
Most bugs on these screens are mix-ups between these four: an empty list treated as “still loading” (endless spinner), an error treated as “empty” (the user thinks their data is gone), or “empty” shown for a moment before the data arrives.
Anatomy of a good one
A good empty or error state answers three questions: what happened, why, and what can I do now?
- 1
Illustration or icon
Sets the tone at a glance (offline, nothing here yet, locked). Decorative, so screen readers skip it.
- 2
Headline: what happened
Plain words, no codes: "We couldn't load your orders", not "Error 503".
- 3
Body: why, and what next
One or two sentences. Says whose problem it is (yours or ours) and what the user can do. No blame, no jargon.
- 4
Primary action
The one thing that fixes it: Retry, Browse restaurants, Clear filters. It must actually work.
- 5
Secondary action (optional)
A way out when the main fix fails: Contact support, Go to Home, Switch account.
- 6
Surrounding UI stays usable
The navigation and header still work, so the user is never trapped on the error.
Designs & variants
Full-screen vs inline
Not every problem deserves a whole screen. Match the size of the message to the size of the problem.
Inline: only one part failed
Sakura Sushi Bar
On the way · arrives 19:35
The order still shows. Only the failed section has a small message and its own Retry.
Full-screen error for a small failure
Something went wrong
Please try again later.
The order tracking worked, but one failed widget wiped out the whole screen.
Use a full-screen state when the screen’s main content is missing (the whole list failed, no permission, offline with no cached data). Use inline messages for one section, one card or one form field, and a banner for ongoing conditions like “You’re offline” while cached content stays visible.
Wording: say it like a human
The words matter as much as the picture. A good error messageError messageText that tells the user something went wrong and, ideally, how to fix it.Open in glossary → is specific, calm and useful:
Plain, specific, actionable
We couldn’t load your orders This is a problem on our side, not yours. Please try again in a moment. [Try again]
Codes, blame or dead ends
Error 500
TypeError: Cannot read properties of undefined (reading 'map')
Invalid request. You did something wrong.
Rules to test against:
- No raw codes or stack tracesStack traceThe list of function calls printed when an error happens, showing where in the code it occurred.Open in glossary →. “500”, “-1009” or “TypeError” mean nothing to users and can leak technical details to attackers. A short reference ID for support (“Ref: 8F3K”) is fine.
- No blame. “Invalid input” or “You did something wrong” for a server problem is both rude and false.
- Say whose problem it is. “Check your connection” (user can fix it) vs “This is on our side” (user should wait).
- Always a next step. Retry, Clear filters, Go to Home, Contact support. Never a dead end.
- Don’t lie. An error shown as “No orders yet” makes users think their data is gone.
Skeletons vs spinners
While data loads, a skeleton (grey shapes in the layout of the real content) feels faster and avoids layout jumps. A spinner is fine for short waits or small areas. Either way, test that the loading state ends: with data, with an empty state, or with an error. A loader that can spin forever is a bug.
Loading (skeleton)
Loaded
- Sakura Sushi Bar2 items · €24.80 · Today, 19:05On the way
- Napoli Pizza Corner3 items · €31.50 · Sat, 20:12Delivered
- Green Bowl1 item · €11.90 · Thu, 12:40Delivered
Try it: state explorer
Here’s one Orders screen in seven states. Switch between them and compare the bad version (things testers report every week) with the good one. In the good version, Retry and Show all orders really work.
Screen state
How testers create it: Sign up with a fresh account that has never ordered.
Bad
Orders
No data
- "No data" is technical and cold
- No explanation and no next step
- Looks like an error
Good
Orders
No orders yet
When you order food, you'll find your orders and receipts here.
- Says what will appear here
- One clear action: Browse restaurants
- Friendly, not alarming
When to use it
Every screen that shows data needs an empty and an error state. Ask for them in reviews if the design only shows the “happy” screen. In particular:
- Lists, feeds and grids: orders, messages, favorites, notifications, search results.
- Screens behind an account or role: team data, admin pages, shared documents.
- Anything that needs the network: which is almost everything.
- Features that need a permission: camera, photos, location, contacts. If the user said no, explain why the feature needs it and how to turn it on in Settings.
Good and bad uses
Banner while cached content stays
- Sakura Sushi Bar2 items · €24.80 · Today, 19:05On the way
- Napoli Pizza Corner3 items · €31.50 · Sat, 20:12Delivered
The user can still read their orders.
Full-screen offline over good data
No connection
Connect to the internet to continue.
The orders were already loaded, but the app hides them the moment the signal drops.
Also don’t use a big empty-state illustration for tiny areas (an empty “Notes” field just needs placeholder text), and don’t use an error state for validation problems in a form: those belong next to the field (see Text field).
Common bugs
| # | Bug | What you see | Typical severity |
|---|---|---|---|
| 1 | Endless spinner | Empty list or failed request, but the loader never stops | High |
| 2 | Dead-end error | “Something went wrong” with no Retry, no explanation, no way back | Medium–High |
| 3 | Retry is broken | Retry does nothing, or sends 2+ requests per tap (duplicate orders on POST!) | Medium–High |
| 4 | Raw technical error | TypeError, 500, null, undefined or a stack trace on screen | Medium |
| 5 | Empty flash | “No orders yet” appears for a moment before the data loads | Low–Medium |
| 6 | Error shown as empty | Offline, 403 or 500 shows “No orders yet”, so users think data was lost | High |
| 7 | Wrong empty message | Filtered list says “Place your first order” instead of “No results for this filter” | Low–Medium |
| 8 | Stale error | “You’re offline” stays after reconnecting, or the error stays after a successful retry | Medium |
| 9 | Two states at once | Data and the empty message both visible, or spinner on top of an error | Low–Medium |
| 10 | Silent for screen readers | Error appears but VoiceOver / TalkBack says nothing; focus stays on a removed button | High (for affected users) |
Find the bug
This food delivery app’s Orders screen has six bugs. Use the device and proxy panel like a real tester:
- Watch the screen closely while it loads, then tap Refresh.
- Try each filter chip.
- Set Map Local to an empty list, then to a 500 error, and tap Refresh each time.
- Turn on Airplane mode, tap Refresh, turn it off again and tap Retry. Watch the network log.
🐞 Bug hunt: Orders screen
This demo has 6 hidden bugs. Explore it like a tester, then check your findings.
Orders
No orders yet
When you order food, you'll find your orders and receipts here.
Device & proxy
Changes apply to the next request: tap Refresh in the app after changing them.
Network log
- Waiting for requests…
- 1
Hint: Set Map Local to "200 · empty list []" and tap Refresh.
- 2
Hint: Set Map Local to "500 server error" and tap Refresh.
- 3
Hint: Go offline, Refresh, then tap Retry once and count the requests in the network log.
- 4
Hint: Tap Refresh (or Restart app) and watch the first half second.
- 5
Hint: Turn Airplane mode on, then off, then load the orders again.
- 6
Hint: Tap each filter chip.
How to test it
How to create each state
You can’t wait for a real outage. Testers force these states on purpose:
- First use. Create a fresh test account (or clear the app’s data / reinstall) and visit every list screen before doing anything else.
- User cleared. Delete, archive or complete every item: empty the cart, read all notifications, remove all favorites.
- No results. Search for nonsense (“zzqx”), combine filters that exclude everything, or filter a date range with no data.
- No permission. Log in with a role that has fewer rights, open a link to someone else’s data, or deny an OS permission (camera, photos, location) when asked. See authorizationAuthorizationChecking what a logged-in user is allowed to see or do.Open in glossary →.
- Offline. Turn on airplane modeAirplane modeA device setting that turns off all wireless connections at once.Open in glossary → before opening a screen, and also while it loads. Then reconnect and see what recovers by itself. More in Network conditions.
- Server errors and edge responses. With a proxy toolProxy toolA program that sits between the app and the server so you can watch and change network traffic.Open in glossary → like Charles or Proxyman, use Map LocalMap LocalA proxy rule that answers a request with a file you choose instead of the real server response.Open in glossary → to return
[], a500, a403, an empty body or broken JSON, or a breakpoint to delay a response by 30 s to trigger a timeoutTimeoutThe time limit after which an app stops waiting for a response and gives up.Open in glossary →. Step by step in Proxy tools.
Checklist
Empty & error state checklist
0 of 12 checked
Example test cases
TC-EES-001First-use empty state for a new accountpositivehigh
Preconditions
- Brand-new account with no orders
Steps
- Log in
- Open the Orders tab
Expected result
After a short loading state, "No orders yet" is shown with an explanation and a "Browse restaurants" button that opens the restaurant list.
TC-EES-002Empty API response ends the loading stateedgehigh
Preconditions
- Proxy Map Local: GET /api/orders returns 200 with body []
Steps
- Open the Orders tab
- Wait 10 seconds
Expected result
The spinner stops within the normal response time and the empty state is shown.
TC-EES-003Server error shows a recoverable error statenegativehigh
Preconditions
- Proxy Map Local: GET /api/orders returns 500
Steps
- Open the Orders tab
- Read the message
- Turn off Map Local
- Tap Try again
Expected result
A friendly message with no codes or technical text and a Try again button. After Try again, exactly one request is sent and the orders appear.
TC-EES-004Offline and back onlinenegativehigh
Preconditions
- Orders were loaded at least once
Steps
- Turn on airplane mode
- Pull to refresh on Orders
- Turn off airplane mode
- Wait 5 seconds
Expected result
While offline, a "You're offline" banner is shown and the saved orders stay visible. After reconnecting, the banner disappears and the list refreshes without user action.
TC-EES-005Filter with no resultsedgemedium
Preconditions
- Account has orders, none of them canceled
Steps
- Open Orders
- Tap the "Canceled" filter
Expected result
"No canceled orders" is shown with a "Show all orders" action. Tapping it shows all orders again.
TC-EES-006No permission is not shown as emptynegativemedium
Preconditions
- Logged in as a team member without the admin role
- Team has orders
Steps
- Open Team orders
Expected result
The screen explains that only admins can see team orders and offers "Request access". It does not say "No orders yet".
TC-EES-007No empty-state flash on a normal loadedgelow
Preconditions
- Account with orders
- Screen recording on (to slow-motion review)
Steps
- Force-close the app
- Open it and go to Orders
- Review the recording frame by frame
Expected result
Only the loading state and then the orders appear. The empty state is never visible, not even for one frame.
TC-EES-008Error state is announced by the screen readerpositivemedium
Preconditions
- VoiceOver or TalkBack is on
- Map Local returns 500
Steps
- Open the Orders tab
- Wait for the error
Expected result
The screen reader announces the error message, and swiping reaches the Try again button. After Try again succeeds, focus lands on the list or its heading, not on nothing.
Tip: click a test case to expand it.
Accessibility
- Announce state changes. When loading ends in an empty or error state, screen reader users must hear it. On iOS and Android, the app should post an accessibility announcement or move focus to the message.
Dev note
On the web, put the message in a live region (role="status"for empty,role="alert"for errors that need attention). - Manage focus after Retry. The Retry button disappears when content loads. Focus should move to the content (or its heading), not get lost at the top of the page.
- The illustration is decorative. It should be hidden from screen readers. The text carries the meaning.
- Loading needs words too. A spinner should have an accessible label like “Loading orders”, and the skeleton itself should be hidden from screen readers.
- Don’t rely on color or icons alone. A red cloud icon without text tells a color-blind or blind user nothing.
- Contrast and text size. Grey “helper” text in empty states is often too light: it still needs 4.5:1, and must not be cut off at large text sizes.
- Buttons are real buttons with at least 44×44 pt / 48×48 dp touch targets and visible keyboard focus on the web.
Mobile vs web
- Many apps follow the system layout for empty and “no results” states: icon, title, description, optional action.
Dev note
SwiftUI has a built-inContentUnavailableViewfor them (iOS 17+). - Test with airplane mode from Control Center, and with Low Data Mode and Wi-Fi with no internet.
- Check that denied permissions (Photos, Location) explain how to re-enable them in Settings, ideally with a button that opens the app’s settings page.
- Use the Network Link Conditioner (developer setting) for “100% Loss” and very slow profiles.
- Material guidance: keep empty states simple, with a short message and, where it helps, one action. Pull-to-refresh is a common recovery path, so test it on error screens too.
- Test with airplane mode, mobile data off for this app (per-app data restriction), and Data Saver.
- On the emulator, use the extended controls (Cellular settings) to simulate no signal or slow networks.
- Rotate the device and resize on tablets and foldables: the empty state should stay centered and readable.
- Use DevToolsDevToolsDeveloper tools built into browsers for inspecting pages, network traffic and errors.Open in glossary → → Network: Offline and throttling presets, Block request URL, and local overrides to return
[]or a 500. - Check the Console for errors while empty or error states show, and make sure nothing technical leaks into the page.
- Web apps can also show error pages (404, 500). Test a broken URL: is there a friendly page with a way back home, and the right HTTP status codeHTTP status codeA three-digit number in every server response: 200 means OK, 404 not found, 500 server error.Open in glossary →?
- Check the browser back button after an error: does it go somewhere sensible?
Check your understanding
Quick quiz
0/6 answered · 0 correct
1.The API returns 200 with an empty list [], but the Orders screen keeps spinning. What kind of bug is this?
2.Which error message is best?
3.A team member without admin rights opens Team orders and sees "No orders yet", but the team has 40 orders. What is wrong?
4.How can a tester make the Orders API return a 500 error on demand?
5.You reconnect after being offline, but the "You're offline" banner stays. What should you check next?
6.For a loading list, what is usually better than a blank screen?
Key takeaways
- Every data screen has four states: loading, data, empty and error. Exactly one should be visible at a time.
- Empty comes in types (first use, user cleared, no results, no permission), and errors in kinds (offline, server error, timeout). Each needs its own message.
- A good state says what happened, why, and what to do next, with a Retry or action that really works, and never shows raw codes or blames the user.
- Force the states yourself: fresh accounts, deleting everything, airplane mode, and proxy tools returning
[], 500 or slow responses. - Always test the way back: reconnect, retry, clear the filter, and check the screen fully recovers.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡