crd-ui

Credit & debit card visualization for your payment forms. Dependency-free. Framework-agnostic. Themeable.

npm i crd-ui

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 }.

PropTypeDescription
numberstring Card number, formatted and masked per brand as you type.
namestring Cardholder name; shows a placeholder while empty.
expirystring Expiry date, normalized to MM/YY.
cvcstring 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.
logosPartial<Record<Brand, string>> Custom inline-SVG brand marks.
onBrandChange(brand: Brand | null) => void React only: fires when the detected brand changes.