DoubtButton

An escalating-confirmation button that makes you click through increasingly absurd, guilt-tripping prompts before the real action finally fires.

DoubtButton never lets you click just once. Each press swaps the label for the next, increasingly absurd confirmation prompt and gives a small shake, while a hairline along the bottom edge tracks how much doubt you have left to power through. Only the click on the final prompt fires onConfirm.

Preview

Props

Variants

One quick doubt

Pass a single confirmation for a lightweight "are you sure?" gate.

Maximum dread

A longer chain for the truly irreversible.

Install

Add the item with the shadcn CLI.

npx shadcn@latest add @evilbuttons/doubt-button

Usage

import { DoubtButton } from "@/components/evil-buttons/doubt-button";

export function ButtonDemo() {
  return (
    <DoubtButton
      label="Delete everything"
      successLabel="Too late."
      onConfirm={() => wipeDatabase()}
    />
  );
}

Props

The component spreads any <button> HTML attributes except onClick.

PropTypeDefaultDescription
childrenReact.ReactNode-Label shown in the idle state. Falls back to label.
labelReact.ReactNode"Delete everything"Idle label used when no children are provided.
confirmationsReact.ReactNode[]6 escalating promptsPrompts shown one per click; the last one fires the action.
successLabelReact.ReactNode"Too late."Label flashed after the final confirmation.
onConfirm() => void-Fired only after the last confirmation is clicked.
resetAfternumber1600Milliseconds to stay in the success state before resetting. Set to 0 to stay.
classNamestring-Extra classes passed to the button.

Notes

  • The user must click confirmations.length + 1 times before onConfirm runs; an empty confirmations array makes it behave like a normal one-shot button.
  • The button shakes on every click via Motion, and the prompt slides in from below.
  • An invisible copy of the widest prompt reserves the button's width so the layout never jumps as labels swap.
  • A hairline progress bar along the bottom edge fills in as you answer each doubt.
  • Styling uses the primary tokens, so it follows your theme; override with className.
  • aria-live="polite" and data-state (idle | doubting | success) expose the current state for screen readers and styling.

Registry

The registry item includes components/evil-buttons/doubt-button.tsx and installs clsx, tailwind-merge, and motion as dependencies.

Search docs

Jump to a button documentation page