TLDR:
window.confirm()silently returnsfalsethe moment Chrome decides to block it — with zero warning to the user. Don't touch it. Use an in-app dialog.
The "quick admin tool" trap
I was building our internal webinar project-management tool for an ecommerce business.
Nothing public-facing. No external users. Just me and a colleague scheduling and tracking webinar events.
So when I needed destructive-action confirmations — "delete this milestone," "reset this task" — I reached for window.confirm().
It's just a quick admin tool. Five seconds vs five minutes.
You can already guess where this is going.
What actually breaks (and why it's the worst kind of break)
Here's the thing nobody tells you about native browser dialogs.
Chrome has a setting: "Prevent this page from creating additional dialogs."
It appears the moment a user clicks "OK" or "Cancel" on any confirm prompt on your page.
And once they click it — intentionally or by accident — every future confirm() call on that page silently returns false.
No error. No console warning. The button just… stops working.
From the user's side, the action does nothing. From the code's side, everything is fine. The bug is INVISIBLE.
That's what makes it brutal. It's not a crash. It's a permanently broken button that looks completely healthy.
What I'd already learned — and ignored
I'd hit this exact wall before, on a finder tool for a cancer education business.
We caught it there, wrote a feedback memory, and built a real pattern: replace every confirm() and alert() with proper in-app components.
The commit in that tool was just applying what we already knew: feat(ux): replace native confirm()/prompt() with in-app dialogs.
Should've done it on day one. I didn't. Classic.
The pattern that actually works
For confirmations, I wire up a Dialog from @/components/ui/dialog (shadcn, the component library we use) with two explicit buttons — Cancel and Confirm.
State lives in the component:
confirmOpen(boolean) controls the dialogresult(string | null) drives a dismissible inline banner after the action runs
The banner matters as much as the dialog.
- Green = full success
- Amber = partial with failures (list the IDs that failed)
- Red = total failure
A resendResult state feeding an inline banner — that's what ConsultationsTab.tsx in the cancer education business project uses (commit 669372c). Same pattern landed in the webinar tool.
Why this matters to me
Admin tools are the worst place to cut UX corners.
They're used every day, by people who don't file bug reports — they just quietly think the product is broken.
The five extra minutes of wiring a real dialog is nothing compared to the "wait, why doesn't this button work anymore?" conversation.
There's no acceptable use case for confirm() in code real humans are going to touch. None.
P.S. If you're running Next.js 16 with shadcn, the full Dialog setup is maybe 20 lines. The inline banner state is another 10. Just do it.