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 | — |
copy | A copyButton is clicked | success, error? |
error | An error occurs | code, message, status?, validationErrors? |
networkChanged | Selected network changes on a co-badged card (cardNumber and card only) | network, previousNetwork, availableNetworks, selectionMode |
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', 'copyButton') |
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)
networks?: string[]; // networks a co-badged card supports (requires BIN lookup or coBadge)
selectedNetwork?: string; // currently selected network on a co-badged card
}
networks and selectedNetwork are populated only when binLookup or coBadge is enabled and the card participates in more than one network.
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;
}
copy
Fired when a copyButton element is clicked. Only emitted by the copyButton element type. The payload reports whether the copy succeeded; it never contains the copied value.
// event.detail shape
{
success: boolean; // true if the value was copied to the clipboard
error?: string; // failure reason (no sensitive data) when success is false
elementType: string; // 'copyButton'
elementId: string;
timestamp: number;
}
In React, subscribe with the onCopy prop on CopyButtonElement:
<CopyButtonElement
allowClipboard
onCopy={(event) => {
if (event.detail.success) {
showToast('Copied to clipboard');
} else {
showToast(`Copy failed: ${event.detail.error}`);
}
}}
/>
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;
}
networkChanged
Fired when the selected payment network changes on a co-badged card — through automatic selection, the built-in selector, or a call to setNetwork(). Only cardNumber and card elements emit this event, and only when binLookup or coBadge is enabled.
// event.detail shape
{
network: string; // the newly selected network
previousNetwork?: string; // the network selected before this change
availableNetworks: string[]; // all networks the card supports
selectionMode: 'auto' | 'user'; // how the selection was made
elementType: string;
elementId: string;
timestamp: number;
}
- Web Elements
- React Elements
cardNumberEl.on('networkChanged', (event) => {
const { network, availableNetworks } = event.detail;
console.log('Network is now', network, 'of', availableNetworks);
});
import { CardNumberElement } from '@basis-theory/react-elements';
<CardNumberElement
id="card-number"
coBadge={{ preferredNetworks: ['cartes-bancaires', 'visa'] }}
onNetworkChanged={(event) => {
const { network, availableNetworks } = event.detail;
console.log('Network is now', network, 'of', availableNetworks);
}}
/>
See Co-Badge Support for the full co-badge workflow, including building your own network picker with setNetwork().
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 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>
);
}