Missing states get improvised during implementation
Aliases: improvised states · implementation default · filling the hole in code
What it is
A state absent from the spec does not remain a hole in code — the shipped artifact has to respond to every input. Implementers therefore fill it with a personal default: disabled as opacity 0.4, error as a red border, loading as a spinner inside the button. Unspecified-state improvisation turns the gap into a family of incompatible private inventions, and those inventions ship as the product.
The claim is about the act of filling the hole and what that act produces, not about which cells are most often blank, and not about how transition paths are written. Once filled, the hole is hard to recognize as a hole.
Why it happens
Implementation is under a hard constraint: events arrive, props are passed, tests click the disabled button. With no design frame, the engineer still writes a branch, or types, accessibility, or pixels will show an unhandled case. Personal defaults come from habit and from whatever neighboring component can be copied that day, not from a product contract, so the same gap is filled differently across repos and iterations.
The fill is unmarked. Opacity 0.4 looks designed; a code review cannot see a missing frame. Only pairing two pages’ disabled states reveals that one is paler and the other greys the whole control and also drops focus. The true shape of a gap is not blankness; it is an uncoordinated family of inventions. When design later draws the official frame, those inventions have to be cleared first, or the official frame will fight what already shipped.
Where it stops holding
Unspecified appearance on platform-native controls (system switches, system alerts) is filled by the OS, not by product engineers. “Improvisation” then sits on the system side; the product either adopts native and accepts those states, or redraws and completes the set itself. Hackathon branches and local drafts may fill temporarily, but before merge the improvised values must be promoted into the spec or the branch deleted. Animation in-betweens that last tens of milliseconds and are never read as a state can skip their own spec, but the start and end states still need one.
Applying it
- Before implementation starts, freeze cells marked as gaps as “must not be privately filled”: the matching branch either blocks release or links to an explicit design task.
- In code review, a new state branch with no matching design frame must get a frame or be deleted. “Opacity for now” is not an option.
- File already-shipped improvisations as debt. Pair screenshots of the same state across pages as the input to paying it down.
- How to check: pick a state that once lacked a frame and search the repo for its implementations. Two or more visual or behavioral answers mean improvisation already happened. Converge to one and write it back into the spec so the fill is a contract, not a private default.
Related
- Same group: R1.03.1 Every component needs a complete interactive state set · R1.03.2 Loading, empty, and error are the states most often left unspecified · R1.03.4 Transitions between states fail more often than the states themselves · R1.03.5 Overlapping states need an explicit priority rule · R1.03.6 States must be controllable and observable from outside the component
- Nearby: R2.05 Completeness of edge-case delivery · R2.04 Design debt
- Search terms:
unspecified-state improvisation·implementation default·design debt
Cards in the same group
- R1.03.1Every component needs a complete interactive state set
- R1.03.2Loading, empty, and error are the states most often left unspecified
- R1.03.4Transitions between states fail more often than the states themselves
- R1.03.5Overlapping states need an explicit priority rule
- R1.03.6States must be controllable and observable from outside the component