Refund Picker

Shows the exact refund before it's issued: units per item, every deduction named, non-refundable items explained, and net amount per refund method.

StopsRefund smaller than the customer expected

Installation

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

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

Usage

refund-picker-usage.tsx
import { RefundPicker } from "@/components/ui/refund-picker"

<RefundPicker
  now="2026-10-13T10:00:00+05:30"
  items={[
    { id: "buds", name: "Wireless earbuds", quantity: 1, unitPrice: 2499, restockingPercent: 10, restockingReason: "opened" },
    { id: "case", name: "Silicone case", quantity: 2, unitPrice: 299 },
    { id: "card", name: "₹500 gift card", quantity: 1, unitPrice: 500, nonRefundable: "Gift cards can't be refunded" },
  ]}
  defaultValue={{ buds: 1, case: 1 }}
  shippingPaid={79}
  returnShippingFee={60}
  methods={[
    { id: "card", label: "Original card", detail: "Visa •••• 4242", days: [5, 7] },
    { id: "upi", label: "UPI", detail: "tushar@okhdfcbank", days: [2, 3] },
    { id: "credit", label: "Store credit", days: [0, 0], waivesFees: true, caveat: "Can't be moved to your bank later" },
  ]}
  onConfirm={() => new Promise((resolve) => setTimeout(resolve, 900))}
/>

What it catches

  • Units per item, never more than were bought
  • A custom amount above what the picked items allow
  • Restocking and return-shipping deductions, each named
  • Non-refundable items, kept visible with the reason
  • Net amount and arrival window per refund method
  • Missing refund method before confirm

Props

RefundPicker props
PropTypeDefault
items* (required)

Items on the order, with quantity, price and refund rules.

RefundItem[]—
methods* (required)

Where the refund can go, with arrival windows and fees.

RefundMethod[]—
value

Controlled units chosen per item id.

Record<string, number>—
defaultValue

Starting units per item id when uncontrolled.

Record<string, number>{}
onValueChange

Called with units per item id after every change.

(picked: Record<string, number>) => void—
method

Controlled refund method id. Defaults to the first method, which should be the original payment.

string—
defaultMethod

Starting refund method id when uncontrolled.

string—
onMethodChange

Called with the chosen refund method id.

(id: string) => void—
shippingPaid

Original shipping charge, refunded when everything is returned.

number0
returnShippingFee

Return-shipping fee deducted from the refund.

number0
now

When the refund is issued, for the arrival dates. Pass it for deterministic rendering; otherwise the clock is read after mount.

Date | string—
currency

ISO 4217 currency code.

string"INR"
locale

Locale for money and date formatting.

string"en-IN"
onConfirm

Issues the refund. Return a promise to show a pending state; a rejection keeps the form.

(refund: { amount: number; method: string; picked: Record<string, number> }) => void | Promise<void>—
messages

Every visible string (see `REFUND_PICKER_MESSAGES`). The single-string props below still work and win.

Partial<RefundPickerMessages>—
titlestring—
confirmLabel(amount: string, method: string) => string—
shippingNote(amount: string) => string—
amountToggleLabelstring—
amountLabel(max: string) => string—
amountCancelLabelstring—
overText(max: string) => string—

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

Accessibility

  • Section labelled by its title; each item's stepper is a labelled group with a polite <output>
  • Custom amount has a <label>, aria-invalid when over the cap, and a polite hint via aria-describedby
  • Refund methods are a labelled radiogroup of native radios
  • Confirm sets aria-busy while pending; icons are aria-hidden

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: Recompute the refundable amount from order data on the server, cap it, and make the refund request idempotent.

  • Arrival dates come from the ranges you pass; banks may take longer
  • Policy (fees, eligibility) is yours to supply; it does not know your return rules
  • Money is computed with JS numbers; round on the server

What it stops

Refund smaller than the customer expected

Also known as

refund · return · partial refund · store credit · restocking fee · order details · admin · returns

Field type

Dependencies

Pairs well with