crd-ui
Credit & debit card visualization for your payment forms. Dependency-free. Framework-agnostic. Themeable.
Try it
Type below — brand detection, formatting and the CVC flip are all built in.
Usage
One package for every framework. The core is vanilla; adapters are subpath imports.
import { useState } from 'react';
import { Card } from 'crd-ui/react';
import 'crd-ui/styles.css';
function PaymentForm() {
const [number, setNumber] = useState('');
const [focused, setFocused] = useState(null);
return (
<>
<Card number={number} focused={focused} />
<input
value={number}
onChange={(e) => setNumber(e.target.value)}
onFocus={() => setFocused('number')}
onBlur={() => setFocused(null)}
/>
{/* name / expiry / cvc inputs alike */}
</>
);
} import { createCard } from 'crd-ui';
import 'crd-ui/styles.css';
const card = createCard(document.querySelector('#preview'), {
number: '',
name: '',
expiry: '',
cvc: '',
});
numberInput.addEventListener('input', (e) => {
card.update({ number: e.target.value });
});
cvcInput.addEventListener('focus', () => card.update({ focused: 'cvc' })); // flips
cvcInput.addEventListener('blur', () => card.update({ focused: null }));
card.brand; // 'visa' | 'mastercard' | … | null
card.destroy(); // remove from the DOM Brands
Live detection for 10 brands, each with its own theme. This grid is rendered by the
vanilla createCard API.
Theming
Override CSS custom properties on .crd or any ancestor.
.crd {
--crd-width: 340px;
--crd-radius: 18px;
--crd-bg: linear-gradient(135deg, #111, #333);
--crd-font: 'SF Mono', monospace;
}
/* Brand themes are plain classes you can redefine entirely */
.crd--brand-visa {
--crd-bg: linear-gradient(135deg, #1a1f71, #4b6cb7);
} The built-in brand marks are deliberately generic so the package ships no trademarked assets — pass your own SVGs if you're licensed to use the official ones:
<Card logos={{ visa: '<svg …>…</svg>' }} /> createCard(el, { logos: { visa: '<svg …>…</svg>' } }); Localization
Every label and placeholder on the card is configurable.
<Card
placeholders={{ name: 'NOMBRE COMPLETO' }}
locale={{ validThru: 'válida hasta' }}
/> createCard(el, {
placeholders: { name: 'NOMBRE COMPLETO' },
locale: { validThru: 'válida hasta' },
}); API
Props of <Card /> — the vanilla createCard(container,
options) accepts the same fields and returns
{ update, brand, element, destroy }.
| Prop | Type | Description |
|---|---|---|
number | string | Card number, formatted and masked per brand as you type. |
name | string | Cardholder name; shows a placeholder while empty. |
expiry | string | Expiry date, normalized to MM/YY. |
cvc | string | Security code, shown on the back. |
focused | 'number' | 'name' | 'expiry' | 'cvc' | null | Highlights the section; 'cvc' flips the card. |
placeholders | { name?: string } | Placeholder text for the empty name. |
locale | { validThru?: string } | Label next to the expiry date. |
logos | Partial<Record<Brand, string>> | Custom inline-SVG brand marks. |
onBrandChange | (brand: Brand | null) => void | React only: fires when the detected brand changes. |