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

# React components - Elements SDK

> RadiumOneProvider, card field components, and hooks for React — the full setup, from provider to submit.

`@cubepay/react-radiumone-js` wraps the core SDK in a provider, field components, and hooks. Install both packages:

```bash theme={null}
npm install @cubepay/radiumone-js @cubepay/react-radiumone-js
```

## `RadiumOneProvider`

Loads the SDK and provides the `RadiumOne` and `Elements` instances to the card components and hooks below it.

Creates one `Elements` group when the SDK resolves and destroys it on unmount.

<ResponseField name="appearance" type="AppearanceOptions">
  Appearance for every Element. Changes after mount are applied live with `elements.update()` (pass a stable object to avoid redundant updates).
</ResponseField>

<ResponseField name="children" type="ReactNode" required>
  Your checkout UI. Card Element components and hooks must be rendered inside.
</ResponseField>

<ResponseField name="locale" type="string" default="the locale passed to loadRadiumOne()">
  Locale for field placeholders and messages. Fixed at mount — later changes log a warning and are ignored.
</ResponseField>

<ResponseField name="onError" type="(error: Error) => void">
  Called once if `radiumone` rejects. The error is also available from `useRadiumOneError`.
</ResponseField>

<ResponseField name="radiumone" type="Promise<RadiumOne | null>" required>
  The promise returned by `loadRadiumOne()`. Create it once at module scope, not inside a component, so re-renders do not reload the SDK.
</ResponseField>

```tsx theme={null}
const radiumonePromise = loadRadiumOne('r1pk_prod_xxx');

export function Checkout() {
  return (
    <RadiumOneProvider radiumone={radiumonePromise} appearance={{ theme: 'flat' }}>
      <PaymentForm />
    </RadiumOneProvider>
  );
}
```

## Components

### `<CardElement>`

Combined card input (number, expiry and CVV in one iframe). Renders a `<div>` container; the ref points to it.

Must be inside `RadiumOneProvider`. Renders an empty container until the SDK has loaded. Do not render it together with the split-field components.

```tsx theme={null}
<CardElement onChange={(e) => setCanPay(e.valid)} options={{ hideCvv: false }} />
```

### `<CardNumberElement>`

Split-field card number input. Use with `CardExpiryElement` and `CardCvvElement`; submitting requires this component.

### `<CardExpiryElement>`

Split-field expiry (MM/YY) input.

### `<CardCvvElement>`

Split-field CVV input. Its length follows the brand detected by `CardNumberElement`.

**Shared props (`BaseElementProps`):**

<ResponseField name="onBlur" type="(event: BlurEvent) => void">
  Called when the field loses focus.
</ResponseField>

<ResponseField name="onChange" type="(event: ChangeEvent) => void">
  Called on every `change` event. Gate your pay button on `event.valid`.
</ResponseField>

<ResponseField name="onFocus" type="(event: FocusEvent) => void">
  Called when the field receives focus.
</ResponseField>

<ResponseField name="onReady" type="(event: ReadyEvent) => void">
  Called when the iframe has loaded and accepts input.
</ResponseField>

<ResponseField name="options" type="CreateElementOptions">
  Creation options. Fixed at mount — later changes log a warning and are ignored; remount the component (for example with a new `key`) to apply them.
</ResponseField>

**`CardNumberElement`-only props (`CardNumberElementProps`):**

<ResponseField name="onCardTypeChange" type="(event: CardTypeChangeEvent) => void">
  Called when the detected card brand changes.
</ResponseField>

Plus any standard `<div>` attribute (`className`, `style`, `id`, and so on) — forwarded to the container element.

## Hooks

### `useRadiumOne()`

The `RadiumOne` instance from the nearest `RadiumOneProvider`.

**Returns** `RadiumOne | null` — The instance, or `null` while loading, after a load failure, or outside a provider (which logs a warning once).

### `useElements()`

The `Elements` group from the nearest `RadiumOneProvider` — call `submit()` and `getState()` on it.

**Returns** `Elements | null` — The group, or `null` until the SDK has loaded.

```tsx theme={null}
const elements = useElements();
const onPay = async () => {
  const result = await elements?.submit(sessionCtx);
};
```

### `useRadiumOneError()`

The error from loading the SDK, if any.

**Returns** `Error | null` — The load error, or `null` while loading or after a successful load.

### `useThreeDS()`

A memoized `ThreeDS` coordinator for 3D Secure.

Stable for the lifetime of the loaded SDK instance. There is no challenge component: the SDK renders challenge UI itself (use `challengeContainer` to choose where). Pass your own `AbortController` signal and `onDecoupled` callback in the options.

**Returns** `ThreeDS | null` — The coordinator, or `null` until the SDK has loaded.

## SSR and Next.js

`loadRadiumOne()` is SSR-safe: it resolves to `null` on the server (no `window`), so it's safe to call at module scope in a file that's also imported server-side. `useRadiumOne()`/`useElements()` return `null` until the client-side load finishes — render your form immediately and let the pay button stay disabled until the elements/field are ready, rather than blocking on a loading screen.

```tsx theme={null}
"use client"; // Next.js App Router: the provider and its children must be client components
```

## Full example

<Tabs>
  <Tab title="Card payment (no 3DS)">
    ```tsx theme={null}
    import { loadRadiumOne } from "@cubepay/radiumone-js";
    import { RadiumOneProvider, CardElement, useElements } from "@cubepay/react-radiumone-js";
    import { useState } from "react";

    const radiumone = loadRadiumOne("r1pk_test_YOUR_KEY");

    export function App() {
      return (
        <RadiumOneProvider radiumone={radiumone}>
          <Checkout />
        </RadiumOneProvider>
      );
    }

    function Checkout() {
      const elements = useElements();
      const [ready, setReady] = useState(false);

      async function pay() {
        if (!elements) return;
        const session = await fetch("/api/create-session", { method: "POST" }).then((r) => r.json());
        try {
          const { token } = await elements.submit({
            sessionId: session.session_id,
            sessionSecret: session.session_secret,
            pubkeyJws: session.pubkey_jws,
          });
          await fetch("/api/charge", { method: "POST", body: JSON.stringify({ token }) });
        } catch (err) {
          if (err.name === "ElementsError") showError(err.customerMessage);
        }
      }

      return (
        <>
          <CardElement onChange={(e) => setReady(e.valid)} />
          <button disabled={!ready} onClick={pay}>Pay</button>
        </>
      );
    }
    ```
  </Tab>

  <Tab title="With 3D Secure">
    ```tsx theme={null}
    import { useElements, useThreeDS } from "@cubepay/react-radiumone-js";

    function Checkout() {
      const elements = useElements();
      const threeDS = useThreeDS();

      async function pay() {
        if (!elements || !threeDS) return;
        const s = await fetch("/api/create-session", { method: "POST" }).then((r) => r.json());
        const { token } = await elements.submit({
          sessionId: s.session_id, sessionSecret: s.session_secret, pubkeyJws: s.pubkey_jws,
        });
        const { status, ref } = await threeDS.authenticate(
          { sessionId: s.session_id, sessionSecret: s.session_secret, cardToken: token },
          { returnUrl: "https://shop.example.com/checkout/3ds-return" },
        );
        if (status === "AUTHENTICATED" || status === "ATTEMPTED") {
          await fetch("/api/charge", { method: "POST", body: JSON.stringify({ token, threeDsRef: ref }) });
        } else {
          showDeclined(status);
        }
      }

      return <button onClick={pay}>Pay</button>;
    }
    ```

    See [3D Secure with Elements](/elements/three-d-secure/add-three-d-secure) for the full flow, including session amount pinning and challenge presentation.
  </Tab>
</Tabs>

## Errors

`ElementsError` (and the 3DS-specific subclasses) are re-exported from `@cubepay/react-radiumone-js` — you don't need a separate import from the core package. See [Elements SDK errors](/elements/errors/error-object-and-handling).
