Grace Send

Gives a short change-of-mind window after pressing Send, so a regretted message or action can be cancelled before it actually goes.

StopsHelps catch a send before second thoughts

Installation

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

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

Usage

grace-send-usage.tsx
import { GraceSend } from "@/components/ui/grace-send"

<GraceSend
  onSend={() =>
    new Promise<void>((resolve) =>
      setTimeout(() => {
        setLog("Sent to 84 people.")
        resolve()
      }, 800),
    )
  }
/>

What it catches

  • Send pressed too early or by mistake
  • Leaving the page during the countdown (beforeunload warning)
  • Held Enter auto-repeating the press
  • Failed send surfaced with an alert

Props

GraceSend props
PropTypeDefault
onSend* (required)

The irreversible action. Runs only after the grace window. Return a promise to show a pending state.

() => void | Promise<void>—
onCancel

Called when the user cancels inside the window.

() => void—
delay

Length of the grace window, in milliseconds.

number5000
children

Idle button content.

React.ReactNode( <> <Send aria-hidden className="size-4" /> Send </> )
disabled

Disables the button.

boolean—
countdownLabel

Templates: `{s}` is the whole seconds left.

string"Sending in {s}s"
hint

Helper text under the button.

string"Press Escape to cancel."
cancelLabel

Cancel button text during the countdown.

string"Cancel"
sendNowLabel

Text of the button that skips the wait.

string"Send now"
pendingLabel

Shown while onSend's promise is pending.

string"Sending…"
sentLabel

Shown after onSend resolves.

string"Sent"
cancelledText

Announced when the user cancels.

string"Cancelled. Nothing was sent."
failedText

Shown when onSend rejects.

string"Couldn't send. Nothing went out, so you can try again."
resetAfter

Milliseconds the "Sent" state shows before resetting. 0 keeps it.

number2000

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

Accessibility

  • Countdown is a labelled group with the seconds in its name; status text in a polite live region and failures in role=alert
  • Escape cancels during the countdown; focus moves to Cancel when it starts and back to Send after
  • Pending button has aria-busy; icons are aria-hidden
  • Ring animation is dropped under reduced motion

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

Limitations

This is a UX guard rail. Enforce on the server: Make the send idempotent on the server; the grace window is client-side only.

  • The delay lives in the open tab; closing the tab or a crash during the window cancels nothing on the server because nothing has been sent
  • Not undo: once onSend runs, the action is out
  • Does not de-duplicate sends on the server

What it stops

Helps catch a send before second thoughts

Also known as

undo send · unsend · send delay · gmail undo send · chat · whatsapp · compose · send button

Field type

Dependencies

Pairs well with