Card number, expiry and CVC in one well: digits group as you type, the brand is detected from the prefix and its mark flips in, focus advances field to field, and it stacks to two rows when narrow.
paymentcredit cardcheckoutbillinginputform
Installation
$npx shadcn@latest add @geturui/card-input
Also installs: Field
Usage
usage.tsx
1import { CardInput } from "@/components/ui/card-input";
2
3export function PaymentDetails() {
4 return (
5 <CardInput
6 label="Card details"
7 name="card"
8 onValueChange={(value, meta) => console.log(meta.brand, meta.complete)}
9 />
10 );
11}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | { number: string; expiry: string; cvc: string } | — | Digits-only value when controlled (expiry as MMYY). |
defaultValue | Partial<CardValue> | — | Initial value when uncontrolled. |
onValueChange | (value: CardValue, meta: CardMeta) => void | — | meta has brand, numberValid (length + Luhn), expiryValid (not past), cvcValid and complete. |
size | "sm" | "md" | "lg" | "md" | Row height, matching Input and Button. |
name | string | — | Prefix for the inputs' names: name-number, name-expiry, name-cvc. |
label / hint / error | ReactNode | — | Field label, helper line and error message. |
required / disabled | boolean | false | Applied to all three inputs. |
detectCardBrand() | (digits: string) => CardBrand | — | The prefix detector, exported for your own summaries. |
Accessibility
- The three inputs sit in a role="group" named by the label, each with its own name (card number, expiry, security code) and the right autocomplete token.
- Brand changes are announced in a polite live region; the brand mark is a labelled image drawn from generic shapes, not real logos.
- Caret position survives reformatting, and Backspace in an empty expiry or CVC moves back to the previous field.