I opened a dropdown last month that had ninety-one options in it. No search field, no grouping, just an alphabetical scroll of “Status” values someone had been quietly appending to for three years. Nobody decided this was a good idea. It simply became the interface, one Jira ticket at a time.
The strange part is that this team knew their psychology. Their design system docs reference Hick’s Law by name. Somewhere in a Figma file, someone drew a beautifully proximity-grouped menu with four clean options. Six sprints later, in production, it was ninety-one items long and nobody could tell you when it happened.
This is the pattern I keep seeing: the psychology isn’t disputed, it’s digested. It survives the design review, ships correctly - and then rots.
The laws were never the problem
Hick’s Law, Miller’s Law, Gestalt proximity - these aren’t controversial. They’re some of the most replicated findings we have in applied psychology, and most engineers I work with can recite them without prompting. Decision time increases with the number of choices. Working memory holds roughly seven items. Things grouped closely are read as related.
The failure isn’t a knowledge gap, it’s a category error. We treat these laws as static constraints to satisfy once, like a linter rule you fix and move past. But a production interface isn’t static. It’s a living, append-only time series of feature requests, each one individually reasonable, none evaluated against the whole.
A UX law is a snapshot judgement: given these seven options, this respects Hick’s Law. Nobody re-runs that judgement when option eight arrives - it doesn’t feel like a redesign, just a small addition. Multiply that by two years of sprints and the “small addition” has quietly become the ninety-one item dropdown.
Why interfaces degrade and code doesn’t (as visibly)
Code decay tends to announce itself. Tests fail, builds slow down, someone opens a profiler. Cognitive decay has no equivalent alarm - there’s no compiler for “this now takes the user 400ms longer to decide.” The system keeps functioning perfectly at the technical layer while degrading badly at the human one, and the two are measured by different teams on different timelines.
A few reasons this keeps happening, in my experience:
- Additions are locally rational. Each new field solves a real ticket for a real customer. The cost lands on everyone else’s cognitive load, which is invisible in a sprint retro.
- Nobody owns subtraction. Adding an option has an advocate - whoever filed the ticket. Removing one needs someone to volunteer for the risk of breaking an edge case nobody’s mentioned in a year.
- The system encodes the pattern, not the constraint. A library ships a
<Select>, not a rule that says “this degrades past nine options.” The shape survives; the reasoning behind it doesn’t travel with it.
The components that violate a UX law hardest are rarely new. They’re the oldest survivors in the codebase - too load-bearing to rewrite, too incrementally modified for anyone to remember their original shape.
The design system team isn’t being precious
The convenient story is that design system teams push back on updates to protect a clean abstraction from feature creep. I used to believe that. Having sat close to a few of these teams, I don’t think it’s usually true.
What’s actually happening: the team has lost track of the fact that a shared component has several points of view, not one - and their real job was never “keep this clean,” it was “keep this equally good across every point of view that depends on it.” That’s the harder job, and it’s the one that stops being done first.
A <Select> is a filter in a data table, a field in a long form, an option in a bulk-action toolbar. Each has a different tolerance for option count, a different failure mode on a slow network. When a request lands to add async loading, the team evaluates it against whichever point of view is loudest in their head. If it works there, it ships - nobody re-checks the filter panel, where the option list now flickers every time an upstream filter changes.
This is why component libraries start behaving oddly rather than failing outright. Nothing breaks in the context you’d test in isolation. It breaks in the seam between contexts - exactly where the design system team is meant to stand watch, and mostly doesn’t, because nobody wrote “own the seams” into the job.
Staying on top of it
A one-time check is the wrong unit of defence against something that decays continuously. A few things that have held up for teams I’ve worked with:
- Give components a decay budget, not just a spec. Document the option count a
<Select>was designed for, and the point at which it should be revisited - a real number, not a vibe. - Assign an owner to subtraction, not just addition. Someone should have “prune the interface” as an explicit, recurring job. Without an owner, deletion never wins against the next feature request.
- Re-run the original judgement, don’t just extend it. When option eight is proposed, ask whether the pattern still satisfies the law it was built to satisfy - a five-minute re-check, not a redesign.
- Instrument choice, not just conversion. Time-to-first-interaction against option count is cheap to log and a genuine early warning.
- Write down every point of view a shared component actually serves. Which surfaces consume it, what each needs, where their tolerances differ - then review changes against that list, not whichever context is top of mind.
The uncomfortable bit
Most UX debt isn’t a design failure, it’s an organisational one. The psychology was correct on day one. What’s missing is a mechanism for the interface to be re-evaluated as often as it’s extended - we’ve built rigorous processes for reviewing what gets added, and almost nothing for asking whether what’s already there still earns its place.
Static laws meeting a dynamic system will always produce decay somewhere. The only real choice is whether you’re measuring it on purpose, or finding out by accident, mid-scroll, wondering how it got there.