# Card Elements

Card Elements is a React library that displays the sensitive details of a [Card](/documentation/api/cards) — 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`](/documentation/api/cards#retrieve-sensitive-details-for-a-card) 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

```shell
$ 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](/documentation/api/cards#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
$ curl -X "POST" --url "https://api.increase.com/cards/${card_id}/create_details_token" \
  -H "Authorization: Bearer ${INCREASE_API_KEY}"
```

```json
{
  "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.

```tsx
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](/documentation/sandbox):

```tsx
<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:

```tsx
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.

```tsx
<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:

```plaintext
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](/documentation/embedded-card-component) renders the whole card, artwork included, in a single iframe whose URL you create with [Create a Card details iframe](/documentation/api/cards#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](/documentation/api/cards#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.
