Web Development

The Worst UX Anti-Patterns and How to Fix Them

Last week I tried to adjust the cookie preferences on a major airline's website. The 'Accept All' button was a bright blue, impossible to miss. The 'Manage Preferences' link? Gray text, 11px font, tucked under a paragraph of legalese. When I finally found it, I landed on a page with 47 individual toggle switches — all defaulted to on — with no 'Reject All' button in sight. I sat there for a full minute clicking toggles before I gave up and just cleared my cookies manually.

This isn't a one-off. It's the norm. Bad UX isn't just annoying — it's hostile. And the worst part? Most of these anti-patterns have dead-simple fixes that take less time to implement than the dark pattern gymnastics they're replacing. I've been building web apps for over a decade, and I'm genuinely baffled that we keep shipping this stuff. So let's talk about the worst offenders and how to kill them.

Dark Patterns That Treat Users Like Marks

Dark patterns aren't accidents. Someone sat in a meeting, looked at conversion metrics, and decided that tricking users was an acceptable business strategy. That's what makes them so infuriating — they're deliberate.

Confirmshaming: Guilt as a UI Element

You've seen these. A modal pops up asking you to subscribe to a newsletter. The accept button says 'Yes, I want to save money!' The decline button says 'No thanks, I prefer paying full price.' This is confirmshaming, and it's everywhere — e-commerce sites, SaaS onboarding flows, even some developer tools. It works on a tiny percentage of users and alienates everyone else.

The fix is embarrassingly simple: use neutral language for both options.

<!-- BEFORE: Manipulative confirmshaming -->
<div class="modal">
<p>Join 50,000 smart developers!</p>
<button class="btn-primary">Yes, sign me up!</button>
<button class="btn-link text-sm">
No thanks, I don't care about my career
</button>
</div>
<!-- AFTER: Respectful, neutral options -->
<div class="modal">
<p>Get weekly frontend tips — no spam, unsubscribe anytime.</p>
<button class="btn-primary">Subscribe</button>
<button class="btn-secondary">No thanks</button>
</div>

Notice the after version also drops the inflated social proof and adds a clear promise about spam. Respect builds more trust than manipulation. Your long-term conversion will thank you.

The Roach Motel: One-Click Signup, Seven-Step Cancellation

Signing up takes 30 seconds. Canceling requires you to navigate to Settings > Account > Subscription > Manage Plan > Cancel Plan > Tell Us Why > Are You Sure > Talk to Retention > Actually Cancel. Some services still require a phone call. In 2026. To cancel a subscription you started with a single click.

I don't care what your retention metrics say. Users who are trapped aren't loyal customers — they're hostages generating chargebacks and one-star reviews. Make cancellation exactly as easy as signup.

  • Put the cancel option in Account Settings, where people expect it
  • Two steps max: 'Are you sure?' then 'Done, your account is canceled'
  • Don't hide the cancel button behind a 'Contact Support' link
  • If you offer a retention discount, fine — but don't make it a required step in the flow
  • Send a confirmation email with a reactivation link instead of making cancellation feel irreversible

Broken Toggle Switches and Confusing UI Controls

Here's a fun experiment: go to your phone's settings and count how many toggles you can't immediately tell are on or off. I'll wait.

The ambiguous toggle is one of the most common UI failures I encounter in code reviews. A toggle should communicate exactly one thing: the current state. Is this on, or is this off? That's it. But developers keep shipping toggles where both states look almost identical, or where the color coding is ambiguous, or where the toggle shows what will happen next instead of what's happening now.

/* BEFORE: Both states look nearly identical */
.toggle {
width: 48px;
height: 24px;
background: #d1d5db;
border-radius: 12px;
cursor: pointer;
}
.toggle.active {
background: #9ca3af; /* barely different from inactive */
}
/* AFTER: Unmistakable state difference + accessible */
.toggle {
width: 48px;
height: 24px;
background: #d1d5db;
border-radius: 12px;
cursor: pointer;
position: relative;
}
.toggle[aria-checked="true"] {
background: #2563eb; /* strong contrast from inactive */
}
.toggle .label {
font-size: 10px;
font-weight: 600;
color: white;
}
.toggle[aria-checked="true"] .label::after { content: "ON"; }
.toggle[aria-checked="false"] .label::after { content: "OFF"; }

Three rules for toggles that don't confuse people: distinct colors for each state (with enough contrast to work for colorblind users), a text label inside or adjacent to the toggle, and proper aria-checked attributes so screen readers can announce the state. If you're relying on color alone, you've failed both UX and accessibility in one move.

Stop Reinventing Native Form Controls

I review a lot of frontend PRs, and one pattern drives me up the wall: custom-built dropdowns that break keyboard navigation. A developer spends two days building a fancy select menu from scratch with a bunch of divs and click handlers. It looks great in the demo. Then a user tries to tab through a form, hits the custom dropdown, and nothing happens. No arrow key support. No type-ahead search. Doesn't work with password managers. Breaks on mobile.

Meanwhile, the native