Payment Button React component
A pay button that shows every step of a payment: the amount, a spinner while it processes, a drawn tick when it's paid, a shake and a clear message when a card is declined, and an optional press-and-hold mode that fills up before it charges.
When to use the Payment Button component
Use Payment Button as the final action of a checkout, a subscription upgrade or a donation form, where people need to see that the payment is happening and whether it worked.
Use hold mode for large or irreversible charges so a stray tap can't pay by accident.
Installation
Add the Payment Button component to your project with the shadcn CLI. It lands in components/payment-button.tsx as plain React + Tailwind CSS source you own and can edit.
npx shadcn@latest add https://reactframe.com/r/payment-button.jsonOr copy the source by hand and install its dependencies:
npm install motion clsx tailwind-mergeProps
<PaymentButton /> accepts 11 props, all optional.
| Prop | Type | Default |
|---|---|---|
| amount | number | 49 |
| currency | string | "USD" |
| locale | string | "en-US" |
| onPay Resolve to finish successfully; reject (or resolve false) to show the error state. | () => Promise<boolean | void> | () => new Promise((r) => setTimeout(() => r(true), 1400)) |
| mode click: pay on click. hold: press and hold for holdMs to pay (stops accidental charges). | "click" | "hold" | "click" |
| holdMs | number | 900 |
| note Text under the button. Set to "" to hide. | string | "Payments are encrypted and processed securely." |
| errorMessage | string | "Card declined" |
| resetAfter Go back to idle this many ms after success or error (0 = stay). | number | 2400 |
| theme | "dark" | "light" | "dark" |
| className | string | — |
- amountnumberDefault: 49
- currencystringDefault: "USD"
- localestringDefault: "en-US"
- onPay
Resolve to finish successfully; reject (or resolve false) to show the error state.
() => Promise<boolean | void>Default: () => new Promise((r) => setTimeout(() => r(true), 1400)) - mode
click: pay on click. hold: press and hold for holdMs to pay (stops accidental charges).
"click" | "hold"Default: "click" - holdMsnumberDefault: 900
- note
Text under the button. Set to "" to hide.
stringDefault: "Payments are encrypted and processed securely." - errorMessagestringDefault: "Card declined"
- resetAfter
Go back to idle this many ms after success or error (0 = stay).
numberDefault: 2400 - theme"dark" | "light"Default: "dark"
- classNamestring
FAQ
- How do I connect it to my payment provider?
- Pass an async onPay that calls your backend (Stripe, Paddle, Lemon Squeezy...). Resolve to show Paid, reject or return false to show the error.
- What happens after success or an error?
- It returns to idle after resetAfter milliseconds (2.4s by default). Set resetAfter={0} to keep the result, for example before redirecting to a receipt.
- Is the hold mode accessible?
- Yes. Holding Space or Enter works like holding the pointer, and the state changes are announced.