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
| Prop | Type | Required | Default | Description |
|---|
apiKey | string | ✅ | — | Vault API key |
pubKey | string | ✅ | — | Vault pub key (client-safe) |
apiUrl | string | — | Production vault URL | Override the Vault API URL |
frameBaseUrl | string | — | https://elements.ozura.com | Override iframe asset URL |
fonts | FontSource[] | — | [] | Custom fonts to load in iframes |
appearance | Appearance | — | — | Global theme and variable overrides. See Styling. |
onLoadError | () => void | — | — | Called if tokenizer iframe fails to load |
loadTimeoutMs | number | — | 10000 | Timeout 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
| Value | Type | Description |
|---|
createToken | (opts?) => Promise<TokenResponse> | Tokenizes all mounted card elements |
ready | boolean | true 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
| Prop | Type | Default | Description |
|---|
layout | 'default' | 'rows' | 'default' | 'default': card number on top, expiry + CVV side by side. 'rows': all three stacked. |
style | ElementStyleConfig | — | Shared 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 |
labelStyle | React.CSSProperties | — | Inline style applied to all label elements |
labelClassName | string | — | CSS class applied to all label elements |
placeholders | { cardNumber?, expiry?, cvv? } | — | Placeholder overrides per field |
gap | number | string | 8 | Gap between fields (px number or any CSS value) |
hideErrors | boolean | false | Suppress the built-in error display |
errorStyle | React.CSSProperties | — | Inline style for the error message container |
errorClassName | string | — | CSS class for the error message container |
renderError | (err: string) => ReactNode | — | Custom error renderer |
onChange | (state: OzCardState) => void | — | Fires on any field change with aggregate state |
onReady | () => void | — | Fires once all three iframes are loaded |
onFocus | (field: 'cardNumber' | 'expiry' | 'cvv') => void | — | Fires when a field gains focus |
onBlur | (field: 'cardNumber' | 'expiry' | 'cvv') => void | — | Fires when a field loses focus |
disabled | boolean | — | Disables all inputs |
className | string | — | CSS 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)
| Prop | Type | Description |
|---|
style | ElementStyleConfig | Style overrides for this field |
placeholder | string | Placeholder text |
disabled | boolean | Disables the input |
loadTimeoutMs | number | Override the iframe load timeout for this field |
className | string | CSS class applied to the wrapper <div> |
onChange | (event: ElementChangeEvent) => void | Fired on value or state change |
onFocus | () => void | Fired when the field receives focus |
onBlur | () => void | Fired when the field loses focus |
onReady | () => void | Fired when the iframe is loaded and interactive |
onLoadError | (error: string) => void | Fired 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