Components/Credit Card Input

Credit Card Input

Grouped card entry with live brand detection (including Elo and Hipercard), Luhn validation, and PCI-minded defaults - the number auto-masks on blur and the CVC is never revealed.


brand: unknown · luhn: false · complete: false

Test numbers: 4242 4242 4242 4242 (Visa), 5555 5555 5555 4444 (Mastercard), 6062 8288 8866 6688 (Hipercard). Fill the number and click outside to see the auto-mask.

Installation

npx @ajax-ui/cli add credit-card-input

Props

  • value / defaultValue - { number, expiry, cvc } as raw digits (expiry is MMYY). Set value + onValueChange for controlled.
  • onValueChange(value, meta) - meta carries brand, luhnValid, and complete.
  • maskOnBlur (default true) - collapses the number to •••• 1234 when focus leaves the field.
  • Helpers exported alongside: detectCardBrand(digits) and luhnCheck(digits).

Brand detection

Visa, Mastercard, Amex, Diners, Discover, plus the Brazilian brands Elo and Hipercard. Brazilian BINs overlap Visa/Discover ranges, so they are matched first. Grouping adapts per brand (Amex 4-6-5, Diners 4-6-4) and so does CVC length (Amex 4). BIN tables are UI-grade hints - the payment processor always has the final say.

Security notes

  • The CVC renders as type="password" and is never displayed.
  • State is held in memory only - nothing is persisted by the component.
  • This is a UI capture component: tokenize with your PSP (Stripe, Adyen, Pagar.me) before the number touches your servers.
  • Native autocomplete="cc-*" attributes keep browser autofill working.

Accessibility

  • Three real <input>s with individual aria-labels - screen readers announce each part.
  • aria-invalid is set on the number once it is full-length but fails Luhn.
  • inputMode="numeric" opens the digit pad on mobile.