Migrating from react-credit-cards
react-credit-cards has not shipped a release since June 2020. Its fork, react-credit-cards-2, is maintained but exposes the same API — so this guide covers both.
The move is close to a drop-in. The four value props and
focused keep their names and their types, so most codebases only change
the import and the stylesheet.
Why it breaks on React 18 and 19
react-credit-cards declares react@^15 || ^16 as a peer dependency, so on
any newer React npm refuses to install it:
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error Found: react@19.3.0
npm error Could not resolve dependency:
npm error peer react@"^15.0.0 || ^16.0.0" from react-credit-cards@0.8.3 --legacy-peer-deps silences the error but not the cause: it's a class
component validated with propTypes, which React 19 no longer checks.
crd-ui supports React 18 and 19 and ships its own TypeScript types.
The swap
Before:
import Cards from 'react-credit-cards';
import 'react-credit-cards/es/styles-compiled.css';
<Cards
number={number}
name={name}
expiry={expiry}
cvc={cvc}
focused={focused}
/>; After:
import { Card } from 'crd-ui/react';
import 'crd-ui/styles.css';
<Card
number={number}
name={name}
expiry={expiry}
cvc={cvc}
focused={focused}
/>; Every prop, mapped
| react-credit-cards | crd-ui | Notes |
|---|---|---|
number | number | Same name, same string. |
name | name | Same name, same string. |
expiry | expiry | Same name; normalized to MM/YY. |
cvc | cvc | Same name, same string. |
focused | focused | Same name and same values ('number' | 'name' | 'expiry' | 'cvc'). |
placeholders | placeholders | Same shape: { name }. |
locale | locale | Key renamed: { valid } becomes { validThru }. |
preview + issuer | layout="display" + brand + last4 | Also reveals real values on demand and can copy them on click. |
callback | onBrandChange | Partial — reports the brand only. See below. |
acceptedCards | — | No equivalent. Gate on the detected brand yourself. See below. |
$rccs-size (SCSS) | --crd-width (CSS var) | No stylesheet to recompile. |
$rccs-shadow | --crd-shadow | Same idea, as a custom property. |
$rccs-card-ratio | — | Fixed at the ISO/IEC 7810 ID-1 ratio. |
The two that aren't drop-in
callback(type, isValid) reported the brand, the max length and validity
in one go. onBrandChange gives you the brand; the rest comes from the
same helpers the library uses internally, so nothing is lost:
// callback(type, isValid) has no direct equal: onBrandChange
// reports the brand. Rebuild the rest from the exported helpers.
import { detectBrand, getBrandSpec } from 'crd-ui';
const onNumber = (value) => {
const brand = detectBrand(value);
if (!brand) return { brand: null, maxLength: 19, isValid: false };
const { lengths, maskLength } = getBrandSpec(brand); // maskLength counts digits
const digits = value.replace(/\D/g, '').length;
return { brand, maxLength: maskLength, isValid: lengths.includes(digits) };
}; acceptedCards has no equivalent — crd-ui always shows what it detects.
Deciding what you accept stays in your code, which also lets you word the rejection
yourself:
// acceptedCards has no equal either — gate on the detected
// brand yourself, which also lets you show your own message.
const ACCEPTED = ['visa', 'mastercard'];
<Card number={number} onBrandChange={(b) => setRejected(!!b && !ACCEPTED.includes(b))} />; Preview mode becomes a layout
preview plus issuer rendered a scrambled card. In crd-ui
that's layout="display", built for dashboards and saved-card lists:
// preview + issuer becomes the display layout, which also
// reveals real values on demand and can copy them on click.
<Card
layout="display"
brand="visa"
last4="4242"
copyable
/>; Theming without SCSS
The $rccs-* SCSS variables required compiling the stylesheet. crd-ui
exposes CSS custom properties instead, so you override them anywhere — including
from Tailwind utilities:
/* The SCSS variables become CSS custom properties, so there is
no stylesheet to recompile: $rccs-size -> --crd-width, and so on. */
.crd {
--crd-width: 290px;
--crd-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
--crd-font: Consolas, Courier, monospace;
} What you gain
- It's maintained. That's the whole reason you're reading this.
- Zero runtime dependencies.
- Not just React. The same component ships for Vue, Svelte and vanilla JS from one package.
- TypeScript types in the box.
- A display layout for cards the user already owns, with click-to-reveal and click-to-copy.
- Six finishes, a 3D hover tilt, and 45 free card backgrounds.