Click Shield

Ignores clicks on a button for a moment after it appears, moves or changes, so people don't press something that just slid under the cursor.

StopsClicked button that just appeared

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/click-shield.json

The files land in your project. They're yours to edit.

Usage

click-shield-usage.tsx
import { ClickShield, type ShieldReason } from "@/components/ui/click-shield"

<ClickShield onBlockedClick={blocked("Cancel subscription")}>
  <button
    type="button"
    onClick={() => setLog("Subscription cancelled.")}
    className={`${button} bg-destructive text-white hover:bg-destructive/90 dark:bg-destructive/60`}
  >
    Cancel subscription
  </button>
</ClickShield>

What it catches

  • Clicks on a control that just mounted
  • Clicks after the control moved under the pointer (layout shift)
  • Clicks right after its label changed, or when the tab comes back
  • Enter/Space key repeats on focus that code moved there

Props

ClickShield props
PropTypeDefault
duration

How long clicks are ignored after a change, in ms. Chrome's dialog guard uses 500.

number—
enabled

Turn the shield off entirely.

boolean—
onBlockedClick

Called for every click the shield swallowed, with the reason it was up.

(reason: ShieldReason) => void—

* required. Also accepts the native props of <span> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Keyboard presses are not delayed unless focus was moved by code
  • Exposes data-shielded for styling; no ARIA changes to the wrapped control

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Swallows real clicks during the window too; keep duration short
  • A timing guard only: it doesn't confirm intent for destructive actions

What it stops

Clicked button that just appeared

Also known as

layout shift · cls · misclick · click protection · late button · dialog appears · ghost click

Dependencies

None beyond React and Tailwind CSS.

Pairs well with