Skip to main content

Element Lifecycle

Elements move through a series of stages from creation to cleanup. Understanding this flow helps you write correct initialization code and avoid common pitfalls.

initialization → creation → mounting → ready → runtime → unmounting

This page explains when each stage occurs and what triggers it. For the full API reference for every element method, see Methods.


Stage 1: Initialization

Create a single SDK instance and share it throughout your application.

import BasisTheory from '@basis-theory/web-elements';

// Synchronous — returns immediately, no await needed
const bt = BasisTheory('<PUBLIC_API_KEY>');

Stage 2: Creation

Create element instances. This is also synchronous and fast — no network requests happen at this point.

const cardNumber = bt.createElement('cardNumber', {
placeholder: '4111 1111 1111 1111',
});

const expiry = bt.createElement('expiry', {
placeholder: 'MM / YY',
});

const cvv = bt.createElement('cvv', {
placeholder: 'CVV',
});

Valid element types are: 'cardNumber', 'expiry', 'cvv', 'text'.


Stage 3: Mounting

Mount attaches the element's iframe to the DOM. You must await mount() — it resolves when the iframe has loaded and is ready for interaction.

// Mount each element to its container
await cardNumber.mount('#card-number-container');
await expiry.mount('#expiry-container');
await cvv.mount('#cvv-container');

// Or mount in parallel
await Promise.all([
cardNumber.mount('#card-number-container'),
expiry.mount('#expiry-container'),
cvv.mount('#cvv-container'),
]);

You can pass a CSS selector string or a direct HTMLElement reference:

const container = document.getElementById('card-number-container');
await cardNumber.mount(container);

Stage 4: Ready

After mounting completes, the element fires a ready event. At this point the user can type into it and you can call focus/blur/clear.

// Subscribe before mounting to avoid missing the event
cardNumber.on('ready', (event) => {
console.log('card number ready', event.detail.elementId);
// Safe to show the form, remove loading state, etc.
});

await cardNumber.mount('#card-number-container');

Stage 5: Runtime

During the interactive phase, users type into elements. The SDK emits change, focus, blur, and error events. You can also call methods like update(), focus(), blur(), and clear().

cardNumber.on('change', (event) => {
const { isValid, isEmpty, cardBrand } = event.detail;
submitButton.disabled = !isValid;
});

// Update options at any time
await cardNumber.update({ disabled: true });

// Programmatic focus
cardNumber.focus();

// Clear the input
cardNumber.clear();

For tokenization at this stage, see Services.


Stage 6: Unmounting

Call unmount() to remove the element's iframe from the DOM. This cleans up event listeners and frees resources. The element instance can be remounted later by calling mount() again.

cardNumber.unmount();
expiry.unmount();
cvv.unmount();

// You can check whether an element is currently mounted
console.log(cardNumber.mounted); // false

Full Web Elements example

import BasisTheory from '@basis-theory/web-elements';

async function initPaymentForm() {
// Stage 1: Initialization
const bt = BasisTheory('<PUBLIC_API_KEY>');

// Stage 2: Creation
const cardNumber = bt.createElement('cardNumber', { placeholder: '4111 1111 1111 1111' });
const expiry = bt.createElement('expiry', { placeholder: 'MM / YY' });
const cvv = bt.createElement('cvv', { placeholder: 'CVV' });

// Stage 4: Subscribe to ready before mounting
let readyCount = 0;
const onReady = () => {
readyCount++;
if (readyCount === 3) showForm();
};
cardNumber.on('ready', onReady);
expiry.on('ready', onReady);
cvv.on('ready', onReady);

// Stage 5: Runtime — track validity
let formValid = false;
cardNumber.on('change', (e) => {
formValid = e.detail.isValid;
document.getElementById('submit').disabled = !formValid;
});

// Stage 3: Mount all in parallel
await Promise.all([
cardNumber.mount('#card-number-container'),
expiry.mount('#expiry-container'),
cvv.mount('#cvv-container'),
]);

// Submit handler
document.getElementById('submit').addEventListener('click', async () => {
const token = await bt.tokens.create({
type: 'card',
data: {
number: cardNumber,
expiration_month: expiry,
expiration_year: expiry,
cvc: cvv,
},
});
console.log(token.id);
});

// Stage 6: Cleanup on page leave
window.addEventListener('beforeunload', () => {
cardNumber.unmount();
expiry.unmount();
cvv.unmount();
});
}

initPaymentForm();

Always unmount elements you no longer need to prevent memory leaks and dangling PostMessage listeners.