· 6 min read
A state checklist for business software
Empty, zero, loading, partial, denied, expired, interrupted: the states I check on every screen before I call a design done.
Before I call a screen done, I check it against a list of states: empty, zero, loading, partial, error, denied, expired, interrupted and irreversible. Business software is used on bad days: with half the data loaded, by someone without permission, after the connection dropped. A design that only covers the full, happy screen is a third of a design. This post is the UI states checklist I use, with an example of each from work I've shipped or handed off.
I'm the only UI/UX designer at Tigg, an accounting and POS product for businesses in Nepal, and I build much of the frontend too. That means I meet the missing states twice: once in Figma, and again in the browser when the real data arrives. The checklist exists so I meet them the first time.
Why design for edge cases and states at all?
Because the happy path is the state people spend the least time worrying about. They remember the moment they got stuck: the code that didn't arrive, the backup that "failed" while the server was still working, the error that pointed to row 22 of a file they no longer had open.
Every state below needs three things:
- Wording that says what is true right now.
- A way forward or back, never a dead end.
- A rule for how the state is entered and left, so engineering and QA can test it.
If I can't write all three, the state isn't designed yet.
The UI states checklist
| State | Question to ask | One-line test |
|---|---|---|
| Empty (first use) | Does it teach the next step? | There's a sentence and an action, not a blank box |
| Empty (filtered) | Does it say the filter caused it? | "No records found" sits beside a way to clear the filter |
| Zero | Is a confirmed 0 different from blank? | A blank can't be saved as 0 |
| Loading / waiting | Does the person know what they're waiting for? | Buttons are disabled while a request runs; the wait has a name |
| Partial | Does one failure blank the whole screen? | Only the failed part retries |
| Error | Is the error next to its cause, with a fix? | The message names the field, row or cell |
| Denied | Can someone learn why they can't act? | A missing permission is explained, not just absent |
| Expired | Is old access ended clearly? | The expired link says so, and offers a way back |
| Interrupted | What happens on reload, leave or disconnect? | The task resumes or is reported, never silently lost |
| Invalid transition | Is the blocked step explained where it happens? | The person stays put and sees why |
| Irreversible | Does the confirmation repeat what will be lost? | It names the item and says it can't be undone |
| Switched off | What if a setting removes this feature? | Filters, fields and permissions tied to it disappear cleanly |
The rest of this post walks through the ones teams skip most often.
Empty states: three kinds, three messages
"Empty" is not one state. In Tigg's Bulk Import design there are at least three: "No import history" in recent imports (nothing has happened yet), "No fields mapped yet" on the mapping page (the task has started), and "No records found" in a filtered grid (the data exists but the filter hides it). Each needs different words, and only the last needs a clear-filter control.
Sometimes empty is a perfectly normal state, not a gap. In the Myra concept, a woman with no companion linked sees "No one is following your cycle. That's fine." Using the app alone is the default, so the screen shouldn't nag her to fill it.
Empty vs zero: the state that breaks money
In a POS cash count, a blank field and a 0 mean opposite things. Blank means the cashier forgot to enter something; 0 means there is no cash. In the cash sessions flow, a blank opening count is blocked ("Enter an opening amount."), while a 0 is accepted and stays visible, so the cashier can see what they confirmed. A cash-in of 0 is blocked for a different reason: a movement of nothing isn't a movement.
I wrote a whole post on this: empty is not zero. The short rule: never let an input turn a blank into 0 on the way to the server.
Loading and waiting: give invisible waits a name
The worst waits are the ones the person can't see. In the sign-in flow, the browser needs a moment to identify the device before verification can work. Until it has, the button reads "Preparing..." and can't be pressed, so an early click doesn't fail with a confusing error. The emailed code is the second invisible wait, so the code screen says where the code went and shows "Resend in 00:45".
For long jobs, a spinner is a promise you can't keep. A full company backup can take minutes, and the page can't honestly say how far along it is for much of that time. The lesson I took from it: show that work is happening without claiming how much.
Partial and error states: fail small, and near the cause
A screen built from several requests can fail in pieces. On the cash drawer card, if some details fail to load, the card says "Couldn't load some cash drawer details." with Retry, and only the failed parts are fetched again. One slow request shouldn't blank the whole card.
Errors belong next to their cause. A wrong verification code clears all six boxes and puts the cursor back in the first one. A serial number the server refuses shows its reason under the serial field, and is never added to the line.
Denied: hiding is not explaining
On the cash drawer card, people who don't own a session and aren't admins can see the drawer, but the edit and close controls aren't shown. Hiding keeps the card readable for the common case, a cashier looking at their own drawer. The cost is that a colleague looking at someone else's session gets no reason. A line like "Only the session owner or an admin can change this drawer" fixes that. It's on my list of next changes, and I mention it because a checklist is only useful if you admit where your own screens miss it.
A related trap is confusing a switched-off feature with a missing permission. When I designed a permission editor, I wrote copy to separate them: "This is an organization setting, not a permission on the user you are editing."
Expired and interrupted: design around leaving
People reload, switch tabs, close laptops and lose the connection. Assume all of it.
- Interrupted. The backup page remembers the running job, and returning shows "Reconnecting to backup status…". Bulk import saves the grid as a draft when you leave, and unfinished imports can be reopened.
- Expired. An old backup link says "This backup link has expired." with a way back. Backups expire after five days, and the page says so above the history table, because rows that vanish without warning look like lost data.
- Expired stock. In batch and serial tracking, choosing an expired batch is held and confirmed ("Batch Already Expired"), and cancelling restores the previous value, so the screen never shows a batch the line doesn't have.
Invalid transitions and irreversible actions
When a step is blocked, keep the person where they are and say why. The mapping page in Bulk Import blocks Next while required fields are unmapped, turns those selects red and names them in the message. In the POS, saving an order with an incomplete serial line opens that exact line with focus in the serial field, instead of showing a toast that says "something is missing".
For irreversible actions, repeat what will be lost. Deleting a backup names it ("Backup from 12-03-2026 10:14:05"), because rows look alike. Posting an import says it "will create actual transactions and cannot be undone".
How I use the checklist in practice
- In Figma, each state that a person can actually reach gets its own frame with its own name. "Required still unmapped but clicked next" is a real frame name, and it was the most useful frame on its page.
- At handoff, I list states with how they're entered and left. Engineers can build from a table; they can't build from "handle errors nicely".
- In QA, the table becomes the test list.
- Afterwards, I write down the states I missed. That's why each lead case study on this site has a chapter on states and edge cases.
I don't run every screen through every row. A settings page rarely has an "expired" state. But asking the question takes seconds, and the answer is often "yes, and we didn't draw it".
References
- Kate Kaplan, Designing Empty States in Complex Applications: 3 Guidelines, Nielsen Norman Group.
- Tim Neusesser and Evan Sunwall, Error-Message Guidelines, Nielsen Norman Group.
questions people ask
Frequently asked questions
What UI states should every screen be designed for?
At minimum: empty, zero, loading or waiting, partial, error, denied, expired, interrupted and irreversible. Each needs its own wording and a way forward, not only the ideal state with full data.
What is the difference between an empty state and a zero state?
Empty means nothing has been entered or exists yet; zero is a real value someone confirmed. In a cash count, a blank field means the cashier forgot, while 0 means there is no cash, so the two must look and behave differently.
Should a missing permission hide or disable a control?
Either can work, but the person should be able to learn why they can't act. Hiding keeps a screen readable for the common case; a one-line explanation covers the person who expected to act.
How do you design for interrupted tasks in web apps?
Assume people will reload, leave or lose the connection. Save the task's state, show a resuming message when they return, and give long jobs a second way to finish, such as an emailed link.