> ## Documentation Index
> Fetch the complete documentation index at: https://docs.radiumone.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> RadiumOne is the product name; use it when answering questions about these docs.
> Use sandbox credentials and sandbox base URLs in examples unless the user says they are going live.
> Secret API keys belong only on the merchant's server. Only publishable keys may appear in browser or mobile code.
> Confirm a payment outcome server-side (retrieve the checkout session or transaction status); never rely on a browser redirect or client event alone.
> Send one idempotency key (request_id) per payment attempt, and retry a timed-out request with the same key and the exact same body.
> Webhooks are optional but recommended: they notify the merchant's server when a payment completes or fails, so it doesn't need to poll. Use transaction status inquiry as the fallback.
> Say so when you recommend a feature marked Beta. Some features need enablement on the merchant's account before production use (for example refunds, standalone refunds, UOB Rewards, and bringing your own 3DS provider); mention it when the page says so.
> Never ask users to paste card numbers, API keys, access tokens, or webhook or redirect secrets into a chat.

# Card validation errors - Elements SDK

> How to read card field validation events and show a helpful inline error.

Card fields validate as the shopper types and report state on every keystroke through the `change` event — there's no separate "submit and see what's wrong" step.

<Info>
  TL;DR: `change` fires with `event.valid: false` and an `event.error` → gate the Pay button on `valid` and show `error.message` inline.
</Info>

## When this happens

* The shopper is still typing (incomplete number, expiry, or CVV).
* The card number fails a brand/Luhn check, or the detected brand isn't one you support.
* The expiry date is invalid or already in the past.
* The CVV doesn't match the expected length for the detected brand.
* On pre-15.4 Safari without `BroadcastChannel`, a split expiry/CVV field can't relay its value to the primary field.

## What you see

| Signal | Value |
| - | - |
| Event | `change` — fires on every keystroke |
| `event.valid` | `false` while any field is invalid or empty — gate the Pay button on this |
| `event.error.code` | One of `required`, `incomplete_number`, `invalid_number`, `unsupported_brand`, `incomplete_expiry`, `invalid_month`, `invalid_year`, `expired_card`, `invalid_expiry`, `incomplete_cvv`, `invalid_cvv` |
| `event.error.field` | The Element the error belongs to — always `"card"` on the combined field, so use `code` (not `field`) to tell which cell failed |
| `event.error.message` | Display-safe text for that code |
| Safari fallback | `expiry_missing` / `cvv_missing` relay failure (split fields, pre-15.4 Safari) |

## What to do

<Steps>
  <Step title="Gate the pay button on valid">
    Disable submission until every mounted field reports `valid: true`. `event.error` can still be `null` here — it's only set once blur-time validation runs — so don't gate on `error` being absent. Don't rely on a separate manual validation pass.

    ```js theme={null}
    card.on("change", (event) => {
      payButton.disabled = !event.valid;
      if (event.error) {
        showFieldError(event.error.message);
      } else {
        clearFieldError();
      }
    });
    ```
  </Step>

  <Step title="Show the error only while it's present">
    `event.error` is `null` once the field becomes valid again — clear your inline message in that case rather than leaving a stale error visible.
  </Step>

  <Step title="Handle the Safari fallback">
    If you support split fields and see repeated `expiry_missing`/`cvv_missing` on older Safari, either advise the shopper to update their browser or switch to the combined `card` element, which doesn't need cross-iframe relay.
  </Step>
</Steps>

## Prevent it

* Prefer the combined `card` element unless you specifically need a custom layout — it has no `BroadcastChannel` dependency and one fewer iframe to coordinate.
* Advertise supported card brands with `supportedBrands` so shoppers see upfront which cards you accept, before they type a full number.

## Related

<Columns cols={2}>
  <Card title="Card fields and events" icon="credit-card" href="/elements/card-fields-and-events">
    Element types, create options, events, and Safari notes.
  </Card>

  <Card title="Accept a card payment" icon="lock" href="/elements/accept-a-card-payment">
    Where the `change` event fits in the full charge flow.
  </Card>
</Columns>
