Nobody ships bad UX on purpose. That's the thing about these mistakes — they don't come from laziness or indifference. They come from building at speed, from defaulting to what the framework gives you, from focusing (reasonably) on whether the feature works rather than how it feels to use.
But intention doesn't change impact. A user staring at an error message that says Something went wrong doesn't care that the developer was under deadline. They just know the product failed them. Here are the five mistakes I see most often — and made myself — with the fix for each one.
The Mistake
Error Messages That Blame the User
✕ What ships
"Invalid input. Please try again."
✓ What it should say
"Your phone number should be 10 digits — no dashes needed."
The Fix
Error messages are a conversation, not a verdict. Every error should answer three questions: what happened, why it happened, and exactly what to do next. Validation errors especially — you know the rule, so tell the user the rule before they break it, not after. Move constraints out of error messages and into helper text visible before submission.
The Mistake
Placeholder Text Doing the Job of a Label
✕ What ships
Input with placeholder="Enter your email address" — no visible label
✓ What it should be
Persistent label above the field + placeholder as hint text only
The Fix
The moment a user starts typing, the placeholder vanishes — and with it, the only instruction they had. This is the single most common form UX failure on the web. Labels belong above the field, always visible, always persistent. Placeholder text is for examples and hints, not for labeling what the field is. This also directly impacts accessibility: screen readers need a proper <label> element, not a placeholder.
The Mistake
Loading States That Appear After the Content
✕ What ships
Content renders → spinner appears briefly → spinner disappears
✓ What it should do
Skeleton UI on mount → content replaces skeleton when ready
The Fix
A loading state that appears after content has already painted isn't a loading state — it's a flash of unnecessary UI that makes the page feel broken. Loading indicators must be present before any content renders, not after. Use skeleton screens that match the layout of the incoming content. They reduce perceived wait time and eliminate the jarring content-then-spinner-then-content sequence that erodes trust.
The Mistake
No Empty States — Just Nothing
✕ What ships
A blank container where data would appear — no message, no guidance
✓ What it should say
"No projects yet. Create your first one to get started. [+ New Project]"
The Fix
Empty states are the most overlooked screens in any product. Every list, every dashboard, every feed has a zero state — and most developers ship nothing there because the design never accounted for it. An empty state is an opportunity, not a gap. It should tell the user exactly what this space is for and give them the one action to fill it. Treat it as onboarding copy, not a blank page.
The Mistake
Disabled Buttons With No Explanation
✕ What ships
Submit button visually greyed out — no tooltip, no explanation why
✓ What it should do
Inline message: "Complete your billing info to continue" — or keep button active, validate on submit
The Fix
A disabled button is a dead end with no sign. The user knows they can't proceed — they have no idea why. Either explain the condition inline before the button, or consider removing the disabled state entirely and showing the validation error on submit instead. The latter approach is often better UX: it lets users attempt the action and receive specific, contextual feedback rather than hunting for the invisible prerequisite.
"Every one of these mistakes has the same root cause: building for the happy path and shipping before the edge cases have faces."
The Quick-Fix Summary
Here's every mistake and its fix in a format you can screenshot, save, or drop into a team Notion doc. Use it as a checklist on your next PR review.
| # | Mistake | Fix in one sentence | Effort |
|---|---|---|---|
| 01 | Error messages that blame | Tell users what happened, why, and exactly what to do next | Low |
| 02 | Placeholder as label | Move instructions to a persistent label above the field, always | Low |
| 03 | Late loading states | Skeleton UI on mount — never show a spinner after content paints | Medium |
| 04 | Missing empty states | Design every zero-state screen with a message and one action | Low |
| 05 | Silent disabled buttons | Explain the condition inline, or remove the disabled state entirely | Low |
The Takeaway
Four of these five fixes are low effort. They don't require a design sprint, a Figma handoff, or a new component library. They require a developer who pauses before closing the ticket and asks: what does a confused user see here? That pause — practiced consistently — is what separates good UX from great UX at the implementation level.
None of these are abstract principles. They're patterns you'll encounter on your very next ticket. The next form you build, the next API call you wrap, the next dashboard widget you scaffold — each one has an empty state, an error state, and a loading state. Ship all three, and you've already put yourself ahead of the majority of production code on the web today.