Start with what the last request established
Use five explicit states:
| State | Evidence | Useful next action |
|---|---|---|
| Not loaded | No result exists yet | Load records |
| Ready | The request returned rows | Inspect or act on named rows |
| Truly empty | A successful unfiltered request returned zero rows | Create the first record, if permitted |
| No matches | Data exists but the named query returned zero rows | Clear or edit filters |
| Unavailable | The request did not establish current contents | Retry or use a defined fallback |
Loading is temporary and should not silently become empty. Unavailable is not proof of zero records. “No matches” should repeat the important search or filter criteria so the operator can understand and reverse them.
Pair each explanation with a relevant action
A create action belongs in a truly empty state only when creation is permitted
and is the logical next step. It is misleading in a no-results state, where the
existing records may simply be hidden by Region = West and Status = Closed.
The primary action there is to change or clear those constraints.
An unavailable state needs the failed operation, the scope affected, and a retry or support path. Do not place ordinary row actions inside it because no current row set was established.
The W3C's status-message guidance uses search progress and “No results returned” as examples of updates that should be programmatically available without moving focus. Announce the whole meaningful message, not only a changing number or icon. Keep the visible heading and control labels descriptive as required by its headings and labels guidance.
Test content before polishing the illustration
Exercise the list with a long record name, missing optional text, an unexpected character sequence, zero total records, zero filtered records, a slow first request, and a failed request. The layout should preserve the record identity, query, status, and next action without relying on color alone.
Acceptance checks
- Loading cannot end as empty unless a successful response established zero rows.
- Empty and no-match messages name different causes and actions.
- The no-match state repeats the important active filters or query.
- Failure never claims the dataset is empty.
- A dynamically changed result count or state is exposed as a status message.
- Long and malformed representative values do not hide the state or action.
- Row actions appear only when their record and freshness contract are known.
These checks specify visible behavior. They do not define the data-fetching, cache, permission, or retry implementation.