Skip to main content
Guides

Card Elements

Card Elements is a React library that displays the sensitive details of a Card — its number, expiration date, verification code, and PIN — inside your own interface. Each value renders in its own <iframe /> served by Increase, so the details never reach your servers or your page’s JavaScript.

Not handling the Card’s primary_account_number yourself is a good practice to help comply with the Payment Card Industry (PCI) Data Security Standards (DSS).

An element renders text and nothing else. You control its typography with an appearance prop; borders, padding, background, and layout belong on your own elements, where your CSS already works.

Install

$ npm install @increasebank/card-elements
# or
$ pnpm add @increasebank/card-elements

React 18.2 or newer is required, including React 19.

Mint a token

On your server, call Create a Card details token for the Card you want to display. A token is scoped to that one Card and expires after an hour.

$ curl -X "POST" --url "https://api.increase.com/cards/${card_id}/create_details_token" \
  -H "Authorization: Bearer ${INCREASE_API_KEY}"
{
  "type": "card_details_token",
  "token": "0f3d2a1b4c5e6f708192a3b4c5d6e7f8",
  "expires_at": "2026-01-31T23:59:59Z"
}

Mint the token in response to an authenticated request from your application and return it only to the cardholder. It is a credential: anyone holding it can read the Card’s details until it expires. Never put your Increase API key in the browser.

Render the elements

Pass the token to CardElementsProvider and place the elements where the values should appear.

import {
  CardCvc,
  CardElementsProvider,
  CardExpiry,
  CardNumber,
} from '@increasebank/card-elements';

export const CardDetails = ({ token }: { token: string }) => (
  <CardElementsProvider
    token={token}
    appearance={{ fontFamily: 'Inter', fontSize: '16px', color: '#ffffff' }}
  >
    <div className="card-art">
      <CardNumber appearance={{ fontSize: '22px', letterSpacing: '0.08em' }} />
      <div className="card-art-details">
        <div>
          <label>Expires</label>
          <CardExpiry />
        </div>
        <div>
          <label>Security code</label>
          <CardCvc />
        </div>
      </div>
    </div>
  </CardElementsProvider>
);

Only the three values are iframes, and each one is transparent, so the background, corner radius, padding, and any chip or network mark are ordinary CSS on .card-art. Each element sizes itself to its text and reports its baseline, so it sits inline with the copy beside it like a normal word.

A token is only valid in the environment that minted it, so say when it came from the sandbox:

<CardElementsProvider token={token} sandbox>
  <CardNumber />
</CardElementsProvider>

Appearance

The appearance prop on the provider sets defaults for every element beneath it. An appearance on an individual element overrides the provider key by key. These keys are supported:

fontFamily, fontSize, fontWeight, fontStyle, fontSmoothing, letterSpacing, lineHeight, color.

Fonts

fontFamily takes a single family. You can use a font that ships with the operating system, one of the Google Fonts that Increase hosts, or a font of your own. The iframe cannot see the @font-face rules on your page, so anything else is a compile error rather than a silent fallback.

System fonts and CSS generics need nothing from you: system-ui, ui-sans-serif, ui-monospace, sans-serif, serif, monospace, Arial, Helvetica, Georgia, Courier New, Times New Roman, Verdana.

Increase hosts Inter, Roboto, Open Sans, Montserrat, Source Sans 3, Figtree, Plus Jakarta Sans, Roboto Mono, and JetBrains Mono. Import HOSTED_FONT_FAMILIES if you want to list them.

For a font of your own, declare it with customFont and hand it to the provider:

import basierCircle from './fonts/BasierCircle-Regular.woff2';
import { CardElementsProvider, customFont } from '@increasebank/card-elements';

const basier = customFont({ family: 'Basier Circle', source: basierCircle });

<CardElementsProvider
  token={token}
  fonts={[basier]}
  appearance={{ fontFamily: basier.family }}
>
  <CardNumber />
</CardElementsProvider>

The element loads the file from your URL, so the request is cross-origin: serve it with an Access-Control-Allow-Origin header, or inline it as a data: URL, which source also accepts. Import the file so your build tooling produces the URL, rather than pasting in a built asset URL that breaks on your next deploy. Increase never stores or serves your font.

Masking

masked replaces the digits with bullets and keeps the separators, so the value keeps its shape: •••• •••• •••• 4242. The card number keeps its last four digits, which you already have from the Card itself. The expiration date, verification code, and PIN are masked completely.

Set it on the provider to mask everything at once and on an element to override that, so a <CardCvc masked /> stays hidden while the rest of the card is revealed.

Masking is a display aid against shoulder-surfing, screen sharing, and screenshots — not an access control. A masked element still fetches the Card’s details.

Copying

copyable makes the value itself a copy control: clicking it, or pressing Enter on it, writes the value to the clipboard. It resolves the same way masked does.

<CardNumber copyable onCopy={() => setNotice('Copied')} />

It copies the real value even while masked, and without the spaces the element renders. onCopy fires once the write succeeds, so the confirmation is yours to display; a refused write reports copy_failed instead.

Formatting

<CardNumber grouping="network" />, the default, spaces the digits the way the Card’s network prints them: four-six-five for American Express and four-four-four-four for Visa. Pass grouping="none" for digits with no spaces.

PINs

<CardPin /> renders the Card’s PIN, for programs configured for cash disbursements. Not every Card has one; when there is no PIN the element reports pin_unavailable and renders nothing.

A PIN is never copyable, so <CardPin /> does not accept copyable or onCopy.

Lifecycle and errors

onReady fires once an element has fetched and rendered its value and reported its size, so it is the right place to dismiss your own loading state. onError receives a code and a message:

token_expired, token_invalid, card_not_found, pin_unavailable, protocol_version_mismatch, initialisation_timeout, element_load_timeout, font_unavailable, copy_failed, network, unknown

font_unavailable and copy_failed are not fatal: the element carries on displaying its value. Every other code means no Card details are shown. element_load_timeout usually means a Content Security Policy is blocking the iframe.

Content Security Policy

The elements load from increase.com, so a page that sends a Content Security Policy needs to allow them:

frame-src https://increase.com

The iframe enforces a policy of its own that permits it to reach only the Increase API, and to load a font from the URL you declared with customFont.

Comparison with the embedded card component

The embedded card component renders the whole card, artwork included, in a single iframe whose URL you create with Create a Card details iframe. It is the faster thing to integrate, and the right choice if you want Increase to lay the card out for you.

Card Elements render one value each and leave the layout and styling to you, so reach for them when the details need to sit inside your own design.

To move an existing integration across, call Create a Card details token where you called create_details_iframe, hand the token to CardElementsProvider instead of putting iframe_url in an <iframe />, and render your own card artwork around the elements.