The design you were given has three screens and they all have data in them. The list has eight rows, the form is filled in, the confirmation is green.
Then you build it, and within an hour you need to know what it looks like while it is loading, what it says when the list is empty, and what happens when the thing the user just clicked was taken by somebody else four seconds ago. None of that is in the design, so you decide it, at four in the afternoon, alone.
The four that get skipped are the four that matter
A screen has more states than it has designs. The ones that get drawn are the ones where everything worked, and the ones that get improvised are the ones a person meets on the day the system is letting them down.
That is exactly backwards. Nobody remembers a list that loaded. Everybody remembers being told “an error occurred” while the thing they wanted disappeared.
So the rule is small and blunt: a feature is not designed until its loading, empty, conflict and failure states are written down. Four extra lines in the document, decided when there is time to think, instead of at the end when there is not.
What the conflict state is really for
Of the four, the conflict is the one teams argue about, because it feels rare. It usually is not. Any system where people choose from a shared pool has a moment where two of them choose the same thing, and if your users mostly act at the same time of day, that moment is not an edge case, it is a Tuesday.
It is also the only state where the system says no to somebody who did nothing wrong. That deserves better than a generic message, and it is where a requirement comes from:
Scenario: somebody else took it first
Given a desk that was free when the page loaded
When a member books it and it has since been taken
Then the board says that desk went, keeps the date they had chosen,
and shows the list already updated
Read that back as three product decisions. The message is specific. The context the user built up is not thrown away. And the screen is usable again immediately, without a reload.
None of that survives being decided at four in the afternoon.
Where the wording belongs
One rule saves more argument than any other: failure wording belongs to the API, not to the screen.
The client renders the message it was sent. It does not invent one, and it does not translate one. Break that and the same failure is worded three ways in three clients, drifts apart, and the support team learns three vocabularies for one event.
It has a useful side effect. Once the wording belongs to the API, a contract test should be strict about the status and loose about the text, because the text is not part of the coupling. A design decision made about a screen turns out to decide what a test asserts, which is the sort of thing you only notice when both are written down.
The cheapest version of this
You do not need a design system or a new document type. You need a table, in whatever file already describes the feature, with one row per state and one sentence about what is on screen and why.
| state | what is on screen | why |
|---|---|---|
| loading | a status the screen reader announces | a blank frame reads as broken |
| empty | the filter, and an empty list | hiding the filter removes the way back |
| conflict | the API’s message, list refreshed | they must be able to pick another |
| failed | the API’s message | never a message invented here |
Writing that took four minutes. It settles arguments that otherwise happen twice, once during build and once during review, and it gives the person building the screen something to check against rather than something to invent. That is the same reason a rule you can execute beats a rule you can read.
If your team’s designs stop at the happy path, that’s the work I do.