Element Events
Elements emit events that let your application respond to user interactions and state changes. All event data is accessed via event.detail.
Quick reference
| Event | Fired when | Payload extras |
|---|---|---|
ready | Element iframe is loaded and interactive | — |
change | Input value changes | isValid, isEmpty, error |
focus | Element receives focus | — |
blur | Element loses focus | — |
error | An error occurs | code, message, status?, validationErrors? |
Subscribing and unsubscribing
element.on() returns an unsubscribe function. Call it to remove the listener.
- Web Elements
- React Elements
// Subscribe
const unsubscribe = cardNumberEl.on('change', (event) => {
const { isValid, isEmpty, cardBrand } = event.detail;
console.log({ isValid, isEmpty, cardBrand });
});
// Unsubscribe when no longer needed
unsubscribe();
In React, use the onChange, onReady, onFocus, onBlur, and onError props directly on element components. The event object passed to each handler is a CustomEvent — access all data via event.detail.
import { CardNumberElement } from '@basis-theory/react-elements';
function PaymentForm() {
return (
<CardNumberElement
id="card-number"
onReady={(event) => console.log('ready', event.detail)}
onChange={(event) => {
const { isValid, isEmpty, cardBrand } = event.detail;
console.log({ isValid, isEmpty, cardBrand });
}}
onFocus={(event) => console.log('focused')}
onBlur={(event) => console.log('blurred')}
onError={(event) => console.error(event.detail.code, event.detail.message)}
/>
);
}
Event payloads
All events include the following base fields in event.detail:
| Field | Type | Description |
|---|---|---|
elementType | string | The type of element that fired the event ('cardNumber', 'cvv', 'expiry', 'text') |
elementId | string | Unique element ID (useful when handling multiple elements) |
timestamp | number | Unix timestamp (milliseconds) when the event fired |
ready
Fired when the element iframe has loaded and the user can interact with it.
// event.detail shape
{
elementType: string;
elementId: string;
timestamp: number;
}
change
Fired whenever the input value changes.
// event.detail shape (all elements)
{
isValid: boolean; // true when the input passes all validation rules
isEmpty: boolean; // true when no characters have been entered
error: ValidationError | null; // null when valid
elementType: string;
elementId: string;
timestamp: number;
}
cardNumber element adds the following fields:
{
cardBrand?: string; // detected brand (e.g. 'Visa', 'Mastercard')
last4?: string | null; // last 4 digits (PCI-safe, for display)
bin?: string | null; // BIN (first 6–8 digits)
cvvLengths?: number[] | null; // expected CVV lengths for the detected brand
potentialBrands?: string[]; // all brands that could match the current input
matchStrength?: number; // confidence score (0–1)
}
focus
Fired when the element receives keyboard focus.
// event.detail shape
{
elementType: string;
elementId: string;
timestamp: number;
}
blur
Fired when the element loses focus.
// event.detail shape
{
elementType: string;
elementId: string;
timestamp: number;
}
error
Fired when an error occurs during element operations (mount failure, API error, etc.).
// event.detail shape
{
code: ErrorCode; // machine-readable code
message: string; // human-readable description
status?: number; // HTTP status (for API errors)
validationErrors?: Record<string, string[]>; // field-level errors from API
elementType: string;
elementId: string;
timestamp: number;
}
ValidationError type
When event.detail.error is not null on a change event, it has this shape:
interface ValidationError {
code: string; // machine-readable error code
message: string; // human-readable error message
}
ErrorCode values
The following values are possible for event.detail.code on the error event:
| Code | Category | Description |
|---|---|---|
MOUNT_ERROR | Infrastructure | Failed to mount the element iframe |
POSTMESSAGE_TIMEOUT | Infrastructure | PostMessage did not receive a response within timeoutMs |
IFRAME_LOAD_ERROR | Infrastructure | Element iframe failed to load |
INITIALIZATION_ERROR | Infrastructure | SDK or coordinator failed to initialize |
API_ERROR | API | Basis Theory API returned an error response |
NETWORK_ERROR | API | Network request failed (no response received) |
VALIDATION_ERROR | Client | Input failed validation before an API request was made |
INVALID_CONFIGURATION | Client | Invalid SDK or element options were provided |
UNKNOWN_ERROR | Client | An unexpected error occurred |
Examples
Track form validity across multiple elements
- Web Elements
- React Elements
const formState = {
cardNumber: { isValid: false },
expiry: { isValid: false },
cvv: { isValid: false },
};
function updateSubmitButton() {
const allValid = Object.values(formState).every((s) => s.isValid);
document.getElementById('submit').disabled = !allValid;
}
cardNumberEl.on('change', (event) => {
formState.cardNumber.isValid = event.detail.isValid;
if (event.detail.cardBrand) {
document.getElementById('card-brand').textContent = event.detail.cardBrand;
}
updateSubmitButton();
});
expiryEl.on('change', (event) => {
formState.expiry.isValid = event.detail.isValid;
updateSubmitButton();
});
cvvEl.on('change', (event) => {
formState.cvv.isValid = event.detail.isValid;
updateSubmitButton();
});
import { useState } from 'react';
import {
CardNumberElement,
ExpiryElement,
CVVElement,
} from '@basis-theory/react-elements';
function PaymentForm() {
const [validity, setValidity] = useState({
cardNumber: false,
expiry: false,
cvv: false,
});
const [cardBrand, setCardBrand] = useState(null);
const isFormValid = Object.values(validity).every(Boolean);
return (
<form>
<CardNumberElement
id="card-number"
onChange={(event) => {
setValidity((prev) => ({ ...prev, cardNumber: event.detail.isValid }));
if (event.detail.cardBrand) setCardBrand(event.detail.cardBrand);
}}
/>
{cardBrand && <span>{cardBrand}</span>}
<ExpiryElement
id="expiry"
onChange={(event) =>
setValidity((prev) => ({ ...prev, expiry: event.detail.isValid }))
}
/>
<CVVElement
id="cvv"
onChange={(event) =>
setValidity((prev) => ({ ...prev, cvv: event.detail.isValid }))
}
/>
<button type="submit" disabled={!isFormValid}>
Pay Now
</button>
</form>
);
}
Handle errors
cardNumberEl.on('error', (event) => {
const { code, message, status } = event.detail;
switch (code) {
case 'MOUNT_ERROR':
case 'IFRAME_LOAD_ERROR':
showError('Could not load the payment form. Please refresh and try again.');
break;
case 'POSTMESSAGE_TIMEOUT':
showError('Request timed out. Check your network connection.');
break;
case 'API_ERROR':
if (status === 400) {
showError('Invalid card details. Please check and try again.');
} else {
showError(`API error ${status}: ${message}`);
}
break;
default:
showError(message);
}
});
Show loading state until all elements are ready
- Web Elements
- React Elements
const ready = { cardNumber: false, expiry: false, cvv: false };
function checkAllReady() {
if (Object.values(ready).every(Boolean)) {
document.getElementById('loading').style.display = 'none';
document.getElementById('form').style.display = 'block';
}
}
cardNumberEl.on('ready', () => { ready.cardNumber = true; checkAllReady(); });
expiryEl.on('ready', () => { ready.expiry = true; checkAllReady(); });
cvvEl.on('ready', () => { ready.cvv = true; checkAllReady(); });
import { useState } from 'react';
import { CardNumberElement, ExpiryElement, CVVElement } from '@basis-theory/react-elements';
function PaymentForm() {
const [ready, setReady] = useState({ cardNumber: false, expiry: false, cvv: false });
const allReady = Object.values(ready).every(Boolean);
return (
<div>
{!allReady && <div className="loading-spinner" />}
<div style={{ visibility: allReady ? 'visible' : 'hidden' }}>
<CardNumberElement id="card-number" onReady={() => setReady((r) => ({ ...r, cardNumber: true }))} />
<ExpiryElement id="expiry" onReady={() => setReady((r) => ({ ...r, expiry: true }))} />
<CVVElement id="cvv" onReady={() => setReady((r) => ({ ...r, cvv: true }))} />
</div>
</div>
);
}