Skip to main content

React Integration

@ozura/elements ships a React entrypoint (@ozura/elements/react) with a context provider, hooks, and pre-built field components for cards.

Installation

npm install @ozura/elements
Import from the React-specific entrypoint:
import {
  OzElements,
  useOzElements,
  OzCardNumber,
  OzExpiry,
  OzCvv,
  OzCard,
} 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 context.
import { OzElements } from '@ozura/elements/react';

export default function App() {
  return (
    <OzElements
      apiKey="YOUR_VAULT_API_KEY"
      pubKey="YOUR_PUB_KEY"
    >
      <CheckoutForm />
    </OzElements>
  );
}

OzElements props

PropTypeRequiredDefaultDescription
apiKeystringVault API key
pubKeystringVault pub key (client-safe)
apiUrlstringProduction vault URLOverride the Vault API URL
frameBaseUrlstringhttps://elements.ozura.comOverride iframe asset URL
fontsFontSource[][]Custom fonts to load in iframes
appearanceAppearanceGlobal theme and variable overrides. See Styling.
onLoadError() => voidCalled if tokenizer iframe fails to load
loadTimeoutMsnumber10000Timeout before onLoadError fires

useOzElements Hook

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

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

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

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

useOzElements return values

ValueTypeDescription
createToken(opts?) => Promise<TokenResponse>Tokenizes all mounted card elements
readybooleantrue when the tokenizer iframe and all mounted field iframes are loaded
useOzElements only wraps card tokenization. For bank tokenization, use the vanilla OzVault API directly — bank elements do not have pre-built React field components. See the Bank Elements guide.

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 } from '@ozura/elements/react';

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

  return (
    <form>
      <OzCard
        layout="default"
        onChange={(state) => {
          console.log(state.complete, state.cardBrand);
        }}
      />
      <button disabled={!ready} onClick={() => createToken({
        billing: { firstName: 'Jane', lastName: 'Doe' },
      })}>
        Pay
      </button>
    </form>
  );
}

OzCard props

PropTypeDefaultDescription
layout'default' | 'rows''default''default': card number on top, expiry + CVV side by side. 'rows': all three stacked.
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
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)
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 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

OzCardState

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

Individual Card Components

import { OzCardNumber, OzExpiry, OzCvv } from '@ozura/elements/react';
The expiry component is exported as OzExpiry, not OzExpirationDate.
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>
  );
}

Individual field props (OzFieldProps)

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

Full Card Checkout Example

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

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

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

    try {
      const { token, cvcSession } = 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 }),
      });

      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
      apiKey="YOUR_API_KEY"
      pubKey="YOUR_PUB_KEY"
      appearance={{ theme: 'flat', variables: { colorPrimary: '#6366f1' } }}
    >
      <CheckoutForm />
    </OzElements>
  );
}

Next Steps