Skip to main content
@ozura/elements/react ships a context provider, hooks, and pre-built field components for both card and bank payments.

Installation

npm install @ozura/elements
Import from the React-specific entrypoint:
import {
  OzElements,
  useOzElements,
  OzCard,
  OzCardNumber,
  OzExpiry,
  OzCvv,
  OzBankCard,
  OzBankAccountNumber,
  OzBankRoutingNumber,
  createFetchWaxKey,
} from '@ozura/elements/react';

OzElements Provider

Wrap your checkout UI with <OzElements>. It creates the OzVault instance and makes it available to all children via React context. fetchWaxKey and pubKey are required.
import { OzElements, createFetchWaxKey } from '@ozura/elements/react';

export default function App() {
  return (
    <OzElements
      pubKey="YOUR_PUB_KEY"
      fetchWaxKey={createFetchWaxKey('/api/mint-wax')}
    >
      <CheckoutForm />
    </OzElements>
  );
}
createFetchWaxKey(url) is a built-in helper that POSTs { sessionId } to your backend URL and expects { waxKey } back. See Server SDK — Mint Wax Handler for the matching route. You can also write the fetchWaxKey callback manually:
<OzElements
  pubKey="YOUR_PUB_KEY"
  fetchWaxKey={async (sessionId) => {
    const res = await fetch('/api/mint-wax', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId }),
    });
    if (!res.ok) throw new Error(`Wax key mint failed (HTTP ${res.status})`);
    const { waxKey } = await res.json();
    if (!waxKey) throw new Error('Missing waxKey in response');
    return waxKey;
  }}
>
  <CheckoutForm />
</OzElements>

OzElements props

PropTypeRequiredDefaultDescription
pubKeystringVault pub key (client-safe)
fetchWaxKey(sessionId: string) => Promise<string>Callback to obtain a session wax key from your backend
frameBaseUrlstringhttps://elements.ozura.comOverride iframe asset URL
fontsFontSource[][]Custom fonts to load in iframes
appearanceAppearanceGlobal theme and variable overrides. See Styling.
onLoadError() => voidCalled if vault fails to load
loadTimeoutMsnumber10000Only relevant when onLoadError is set: ms to wait before calling onLoadError
onWaxRefresh() => voidCalled when the SDK silently re-mints the wax key during a tokenization attempt
onReady() => voidCalled once when the vault is ready
maxTokenizeCallsnumber3Max successful tokenize calls per wax key before proactive refresh. Must match maxTokenizeCalls in your server-side mintWaxKey call.
childrenReactNodeYour checkout UI
Mount one card form at a time inside a single <OzElements> provider. If you render a second <OzCard> (or a second set of individual field components) without first unmounting the first, the older element iframes will be silently replaced. For multi-step flows, unmount one form before mounting the next.

useOzElements Hook

Use useOzElements() inside any component wrapped by <OzElements> to tokenize and track readiness.
import { useOzElements } from '@ozura/elements/react';

function CheckoutForm() {
  const { createToken, ready, initError } = useOzElements();

  if (initError) return <p>Payment unavailable — please refresh.</p>;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const { token, cvcSession, billing } = await createToken({
      billing: { firstName: 'Jane', lastName: 'Smith' },
    });
    // send token + cvcSession + billing to backend
  };

  return (
    <form onSubmit={handleSubmit}>
      <OzCardNumber />
      <OzExpiry />
      <OzCvv />
      <button type="submit" disabled={!ready}>Pay now</button>
    </form>
  );
}

useOzElements return values

ValueTypeDescription
createToken(opts?: TokenizeOptions) => Promise<TokenResponse>Tokenizes all mounted card elements
createBankToken(opts: BankTokenizeOptions) => Promise<BankTokenResponse>Tokenizes mounted bank account elements
readybooleantrue when the vault and all mounted fields are ready. Gate your submit button on this.
initErrorError | nullNon-null when vault initialization fails — e.g. pubKey is missing, fetchWaxKey threw, returned an empty string, or the backend was unreachable. Render a fallback UI when set.
tokenizeCountnumberCount of successful createToken/createBankToken calls against the current wax key. Resets to 0 on every wax key refresh. Use this to display “X attempts remaining” feedback or disable the pay button while a background key refresh is completing.
ready is a composite flag. It is true only when the vault is ready AND every field component that has been mounted has also finished loading. Do not use vault.isReady directly — that only reflects vault readiness, not field readiness.

OzCard — Combined Card Component

OzCard renders card number, expiry, and CVV in a single component with built-in layout, loading skeletons, and inline error display. For maximum layout control, use the individual components instead.
import { OzElements, OzCard, useOzElements, createFetchWaxKey } from '@ozura/elements/react';

function CheckoutForm() {
  const { createToken, ready } = useOzElements();

  return (
    <form onSubmit={async (e) => {
      e.preventDefault();
      const { token, cvcSession } = await createToken({
        billing: { firstName: 'Jane', lastName: 'Doe' },
      });
      // send to backend
    }}>
      <OzCard layout="default" />
      <button type="submit" disabled={!ready}>Pay</button>
    </form>
  );
}

export default function App() {
  return (
    <OzElements pubKey="YOUR_PUB_KEY" fetchWaxKey={createFetchWaxKey('/api/mint-wax')}>
      <CheckoutForm />
    </OzElements>
  );
}

OzCard props

PropTypeDefaultDescription
layout'default' | 'rows''default''default': card number on top, expiry + CVV side by side. 'rows': all three stacked vertically.
styleElementStyleConfigShared style applied to all three fields
styles{ cardNumber?, expiry?, cvv? }Per-field style overrides merged on top of style
classNames{ cardNumber?, expiry?, cvv?, row? }CSS class names for field wrappers and the expiry+CVV row container
labels{ cardNumber?, expiry?, cvv? }Labels rendered above each field
labelStyleReact.CSSPropertiesInline style applied to all label elements
labelClassNamestringCSS class applied to all label elements
placeholders{ cardNumber?, expiry?, cvv? }Placeholder overrides per field
gapnumber | string8Gap between fields (px number or any CSS value string)
hideErrorsbooleanfalseSuppress the built-in error display
errorStyleReact.CSSPropertiesInline style for the error message container
errorClassNamestringCSS class for the error message container
renderError(err: string) => ReactNodeCustom error renderer
onChange(state: OzCardState) => voidFires on any field change with aggregate state
onReady() => voidFires once all three field iframes are loaded
onFocus(field: 'cardNumber' | 'expiry' | 'cvv') => voidFires when a field gains focus
onBlur(field: 'cardNumber' | 'expiry' | 'cvv') => voidFires when a field loses focus
disabledbooleanDisables all inputs
classNamestringCSS class for the outer wrapper div

OzCardState

type OzCardState = {
  complete:   boolean;                      // all three fields complete and valid
  cardBrand?: string;                       // detected brand from card number field
  error?:     string;                       // first error from any field
  fields: {
    cardNumber: ElementChangeEvent | null;
    expiry:     ElementChangeEvent | null;
    cvv:        ElementChangeEvent | null;
  };
};

Individual Card Components

Use OzCardNumber, OzExpiry, and OzCvv when you need full layout control.
The expiry component is exported as OzExpiry, not OzExpirationDate.
import { OzCardNumber, OzExpiry, OzCvv } from '@ozura/elements/react';

function CardFields() {
  return (
    <div className="card-form">
      <div className="field">
        <label>Card number</label>
        <OzCardNumber />
      </div>
      <div className="row">
        <div className="field">
          <label>Expiry</label>
          <OzExpiry />
        </div>
        <div className="field">
          <label>CVV</label>
          <OzCvv />
        </div>
      </div>
    </div>
  );
}

OzFieldProps (all individual field components)

PropTypeDescription
styleElementStyleConfigStyle overrides for this field
placeholderstringPlaceholder text
disabledbooleanDisables the input
loadTimeoutMsnumberOverride the iframe load timeout for this field
classNamestringCSS class applied to the wrapper <div>
onChange(event: ElementChangeEvent) => voidFired on value or state change
onFocus() => voidFired when the field receives focus
onBlur() => voidFired when the field loses focus
onReady() => voidFired when the iframe is loaded and interactive
onLoadError(error: string) => voidFired if the iframe fails to load

OzBankCard — Combined Bank Component

OzBankCard renders account number and routing number in a single component with built-in layout, loading skeletons, and inline error display.
import { OzElements, OzBankCard, useOzElements, createFetchWaxKey } from '@ozura/elements/react';

function BankForm() {
  const { createBankToken, ready } = useOzElements();
  const [firstName, setFirstName] = React.useState('');
  const [lastName, setLastName]   = React.useState('');

  return (
    <form onSubmit={async (e) => {
      e.preventDefault();
      const { token, bank } = await createBankToken({ firstName, lastName });
      // send token to your ACH processor backend
    }}>
      <input value={firstName} onChange={e => setFirstName(e.target.value)} placeholder="First name" />
      <input value={lastName}  onChange={e => setLastName(e.target.value)}  placeholder="Last name" />
      <OzBankCard />
      <button type="submit" disabled={!ready}>Submit</button>
    </form>
  );
}

export default function App() {
  return (
    <OzElements pubKey="YOUR_PUB_KEY" fetchWaxKey={createFetchWaxKey('/api/mint-wax')}>
      <BankForm />
    </OzElements>
  );
}

OzBankCard props

PropTypeDefaultDescription
styleElementStyleConfigShared style applied to both fields
styles{ accountNumber?, routingNumber? }Per-field style overrides
classNames{ accountNumber?, routingNumber? }CSS class names for field wrappers
labels{ accountNumber?, routingNumber? }Labels rendered above each field
labelStyleReact.CSSPropertiesInline style applied to all label elements
labelClassNamestringCSS class applied to all label elements
placeholders{ accountNumber?, routingNumber? }Placeholder overrides per field
gapnumber | string8Gap between fields
hideErrorsbooleanfalseSuppress the built-in error display
errorStyleReact.CSSPropertiesInline style for the error message container
errorClassNamestringCSS class for the error message container
renderError(err: string) => ReactNodeCustom error renderer
onChange(state: OzBankCardState) => voidFires on either field change with aggregate state
onReady() => voidFires once both field iframes are loaded
onFocus(field: 'accountNumber' | 'routingNumber') => voidFires when a field gains focus
onBlur(field: 'accountNumber' | 'routingNumber') => voidFires when a field loses focus
disabledbooleanDisables all inputs
classNamestringCSS class for the outer wrapper div

OzBankCardState

type OzBankCardState = {
  complete: boolean;                          // both fields complete and valid
  error?:   string;                           // first error from either field
  fields: {
    accountNumber: ElementChangeEvent | null;
    routingNumber: ElementChangeEvent | null;
  };
};

Individual Bank Components

Use OzBankAccountNumber and OzBankRoutingNumber for full layout control. They accept the same OzFieldProps as card fields.
import { OzBankAccountNumber, OzBankRoutingNumber } from '@ozura/elements/react';

function BankFields() {
  return (
    <>
      <div>
        <label>Account number</label>
        <OzBankAccountNumber placeholder="Account number" />
      </div>
      <div>
        <label>Routing number</label>
        <OzBankRoutingNumber placeholder="Routing number" />
      </div>
    </>
  );
}

Full Card Checkout Example

import { useState } from 'react';
import { OzElements, useOzElements, OzCard, createFetchWaxKey } from '@ozura/elements/react';
import { OzError } from '@ozura/elements';

function CheckoutForm() {
  const { createToken, ready, initError } = useOzElements();
  const [error, setError]   = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  if (initError) return <p>Payment fields unavailable — please refresh.</p>;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError(null);
    setLoading(true);

    try {
      const { token, cvcSession, billing } = await createToken({
        billing: {
          firstName: 'Jane',
          lastName:  'Smith',
          address: {
            line1:   '123 Main St',
            city:    'San Francisco',
            state:   'CA',
            zip:     '94102',
            country: 'US',
          },
        },
      });

      const res = await fetch('/api/charge', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token, cvcSession, billing }),
      });

      if (!res.ok) throw new Error('Payment failed');
      window.location.href = '/success';
    } catch (err) {
      setError(err instanceof OzError ? err.message : 'Something went wrong.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="checkout-form">
      <OzCard layout="default" />
      {error && <p className="error">{error}</p>}
      <button type="submit" disabled={!ready || loading}>
        {loading ? 'Processing…' : 'Pay'}
      </button>
    </form>
  );
}

export default function App() {
  return (
    <OzElements
      pubKey="YOUR_PUB_KEY"
      fetchWaxKey={createFetchWaxKey('/api/mint-wax')}
      appearance={{ theme: 'flat', variables: { colorPrimary: '#6366f1' } }}
      onLoadError={() => console.error('Payment fields failed to load')}
    >
      <CheckoutForm />
    </OzElements>
  );
}

Next Steps

Styling

Customize field appearance with style config and global appearance.

Error Handling

Handle OzError codes and surface them to users.

Card Elements Reference

All card options, events, and methods.

Bank Elements Reference

All bank options, events, and methods.