Card Input

Card number, expiry and CVC in one control, with brand detection and a Luhn check. Client-side validation catches the typo; it never claims the card is good.

src/components/ui/card-input.tsx

About

From the component's own source, where it was written.

Card number, expiry and CVC as one control.

The number is validated with the Luhn checksum, which catches a mistyped digit before the request is made. That matters commercially, not just for polish: failed authorisations count against a merchant's decline rate, and card networks price on it.

The brand is detected from the leading digits so the CVC length can follow — American Express uses four, everyone else three, and a fixed maxlength of three silently truncates an Amex code.

Nothing here stores or transmits anything. In production the fields belong in a PSP iframe so the page never touches a PAN; this is the shape for a demo, a test harness, or a PSP that hands you styling control.

Install

npx astralyx-ui add card-input

Usage

import { CardInput } from '@/components/ui/card-input'

<CardInput onCardChange={(card) => setCard(card)} />

Composer

Change the props and the source below follows.

Props
tsx
<CardInput onCardChange={setCard} />

Examples

Default and small

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

CardInput

Extends Omit<ComponentProps<'div'>, 'onChange'>.

PropTypeDefaultDescription
onCardChange(card: { number: string expiry: string cvc: string brand: CardBrand valid: boolean }) => void—Fires with { number, expiry, cvc, brand, valid } on every keystroke. valid means the digits pass Luhn and the expiry is in the future — not that the card will authorise.
sizekeyof typeof fieldSize'md'—
numberLabelstring'Card number'Accessible name for the number field.
expiryLabelstring'Expiry date'—
expiryPlaceholderstring'MM/YY'—
cvcLabelstring'Security code'—
invalidNoteReactNode'That card number does not check out — one of the digits is wrong.'Shown when the digits fail the Luhn check.
disabledbooleanfalse—
securitynone stored—The component holds the number in local state only, and never in a named field that a form serialiser would sweep up.

Functions

FunctionSignatureDescription
detectBrand(digits: string) => CardBrandBrand from the issuer identification number.
luhnValid(digits: string) => booleanLuhn checksum — catches a single mistyped digit and most transpositions.

Types

TypeDefinitionDescription
CardBrand
'visa' | 'mastercard' | 'amex' | 'discover' | 'unknown'
Card number, expiry and CVC as one control.

Behaviour

TopicDetailDescription
brand detectionautomaticFrom the issuer identification number, which also sets the expected length and the CVC length: Amex is 15 digits with a 4-digit code.
focusautomaticAdvances to expiry and then CVC as each segment fills, and steps back on backspace from an empty field.