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.