Skip to main content

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​

EventFired whenPayload extras
readyElement iframe is loaded and interactive—
changeInput value changesisValid, isEmpty, error
focusElement receives focus—
blurElement loses focus—
copyA copyButton is clickedsuccess, error?
errorAn error occurscode, message, status?, validationErrors?
networkChangedSelected 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.

// Subscribe
const unsubscribe = cardNumberEl.on('change', (event) => {
const { isValid, isEmpty, cardBrand } = event.detail;
console.log({ isValid, isEmpty, cardBrand });
});

// Unsubscribe when no longer needed
unsubscribe();

Event payloads​

All events include the following base fields in event.detail:

FieldTypeDescription
elementTypestringThe type of element that fired the event ('cardNumber', 'cvv', 'expiry', 'text', 'copyButton')
elementIdstringUnique element ID (useful when handling multiple elements)
timestampnumberUnix 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 enrichment)
selectedNetwork?: string; // currently selected network on a co-badged card
binDetails?: BinDetails; // issuer metadata from BIN enrichment
}

The card element emits the same cardBrand, last4, bin, networks, selectedNetwork and binDetails fields, plus an errors array covering all three sub-fields.

BinDetails​

Returned on the change event when BIN enrichment is on. Every field is optional — enrichment coverage varies by BIN, and a lookup that returns nothing usable omits binDetails entirely.

{
brand?: string; // card brand from enrichment, kebab-case (e.g. 'visa', 'cartes-bancaires')
type?: 'debit' | 'credit' | 'prepaid'; // funding type; omitted when not reported
country?: string; // issuing country, ISO 3166-1 alpha-2
bank?: string; // issuing bank name
segment?: string; // 'Consumer', 'Commercial', 'Business' or 'Government'
}

Two things to design around. binDetails arrives asynchronously — the lookup is debounced and then makes a network call, so it is absent for a short window after the sixth digit and stays absent if the lookup finds nothing. And it is scoped to the first six digits: unlike networks, it is not narrowed as more digits are entered.

binDetails.brand comes from the issuer's BIN record, while the top-level cardBrand comes from local pattern matching and is available immediately. They normally agree; prefer cardBrand for responsive UI and binDetails.brand when you need the issuer's own answer.

networks and selectedNetwork are populated only when 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;
}
cardNumberEl.on('networkChanged', (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:

CodeCategoryDescription
MOUNT_ERRORInfrastructureFailed to mount the element iframe
POSTMESSAGE_TIMEOUTInfrastructurePostMessage did not receive a response within timeoutMs
IFRAME_LOAD_ERRORInfrastructureElement iframe failed to load
INITIALIZATION_ERRORInfrastructureSDK failed to initialize
API_ERRORAPIBasis Theory API returned an error response
NETWORK_ERRORAPINetwork request failed (no response received)
VALIDATION_ERRORClientInput failed validation before an API request was made
INVALID_CONFIGURATIONClientInvalid SDK or element options were provided
UNKNOWN_ERRORClientAn unexpected error occurred

Examples​

Track form validity across multiple 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();
});

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​

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(); });