# Card Field

> Card details as one group: the number, the expiry (MM / YY), and the security code, with one line of help or error under it. The number groups itself as it is typed and names the brand in words.

- Name: `card-field`
- Status: beta
- Categories: forms
- File: the install writes `card-field.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/field-frame`, `@arbokui/input`, `@arbokui/status-text`. The install adds them.
- Gallery: https://arbokui.com/components/card-field
- Registry item: https://arbokui.com/r/card-field.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/card-field
   ```

## Use it for

- Taking a card to pay for an order, a plan, or a booking.
- It is the look and the checks, for demos and prototypes. In production, use the payment provider's own hosted fields, so card numbers never reach your server.
- Set showErrors when Pay is pressed, rather than disabling Pay. Every problem then shows at once.
- When the bank says no, pass declined with why, like "Nothing was taken.", and onRetry. Clear it when the details change.
- Offer saved cards as selection cards above it, and this field for a new one.

## Avoid it for

- Sending card numbers to your own server. Use the payment provider's hosted fields.
- Bank details or other numbers. Use an input.
- Showing a saved card. Show its brand and last four digits as text.

## Props

- `value`: `{ number: string; expiry: string; cvc: string }`. The parts as typed, like "4242 4242 4242 4242", "12 / 28", and "123". Pass it with onValueChange to control the field.
- `defaultValue`: `Partial<CardValue>`. The parts to start with, when the field keeps its own value.
- `onValueChange`: `(value: CardValue, info: { brand: "Visa" | "Mastercard" | "Amex" | null; last4: string; complete: boolean }) => void`. Called on each change, with the brand, the last four digits, and whether every part is complete and right.
- `label`: `ReactNode`. Names the group, above it. Pressing it focuses the number. Without one, the group is named "Card details".
- `hint`: `ReactNode`. A short note under the group.
- `error`: `ReactNode`. Other problems, in red under the group, with red rings on every part.
- `showErrors`: `boolean`. Default `false`. Shows every problem, empty parts too. Set it when Pay is pressed.
- `declined`: `boolean | string`. The bank declined it. "Declined" shows in red, then the string says why, in ink.
- `onRetry`: `() => void | Promise<unknown>`. With declined: a retry icon ending the line, named "Try again". Return a promise, and it waits on it.
- `today`: `string`. Today for the expiry check, as "2026-09-29", for demos and tests. Default: the browser's today, once the field is on the page.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Height and text size of every part. Horizontal padding stays aligned across sizes.
- `disabled`: `boolean`. Default `false`. Turns every part off.
- `ref`: `Ref<HTMLInputElement>`. Reaches the card number input, where focus starts.
- `id`: `string`. Lands on the group, with aria-\* and data-\*. Its aria-describedby joins the note's.
- `className`: `string`. Classes for the frame around the label, the group, and the note.

## Accessibility

- The three parts sit in a group named by the label, or "Card details".
- Each part has its own name: "Card number", "Expiry date, MM / YY", and "Security code".
- The brand shows in words, never a logo, and describes the number input. It is said politely when it changes.
- A part with a problem is marked invalid and described by the note under the group, which is announced when it appears.
- Focus moves on once a part is complete and right. Backspace in an empty part goes back to the one before.
- The parts use the browser's card autofill and a number keypad, and take no name, so a plain form never posts the digits.

## Examples

### Start with a test card

```tsx
<CardField
  label="Card"
  defaultValue={{ number: "4242 4242 4242 4242", expiry: "12 / 28", cvc: "123" }}
  today="2026-09-29"
/>
```

### The bank declined it

```tsx
<CardField
  label="Card"
  defaultValue={{ number: "4000 0000 0000 0002", expiry: "12 / 28", cvc: "123" }}
  declined="Nothing was taken."
  onRetry={() => {}}
  today="2026-09-29"
/>
```
