Element Types
v3 provides six element types. Each renders as a sandboxed iframe. The combined card element and the four individual input elements share the same core interface; copyButton is an action button with its own options and events.
| Type | createElement key | Description |
|---|---|---|
| Card | 'card' | Combined card input — number, expiry, and CVV in a single iframe |
| Card Number | 'cardNumber' | PAN input with Luhn validation and brand detection |
| Expiry | 'expiry' | MM/YY expiration date input |
| CVV | 'cvv' | Card verification code input |
| Text | 'text' | General-purpose single-line text input |
| Copy Button | 'copyButton' | Secure button that copies an element's value to the clipboard |
Shared Options
All elements accept these options in createElement and element.update():
| Option | Type | Default | Description |
|---|---|---|---|
placeholder | string | — | Placeholder text shown when the input is empty |
ariaLabel | string | Element-specific | ARIA label for screen readers |
disabled | boolean | false | Disables the input |
readOnly | boolean | false | Makes the input read-only |
Shared Methods
Every element exposes the same interface:
| Method | Signature | Description |
|---|---|---|
mount | (selector: string | HTMLElement) => Promise<void> | Attaches the iframe to the DOM; resolves when the element is interactive |
unmount | () => void | Removes the iframe from the DOM and clears cached values |
update | (options: Partial<ElementOptions>) => Promise<void> | Updates options on a mounted element |
focus | () => void | Focuses the input |
blur | () => void | Blurs the input |
clear | () => void | Clears the current value |
on | (event, listener) => () => void | Subscribes to an event; returns an unsubscribe function |
Properties:
| Property | Type | Description |
|---|---|---|
id | string | Unique element identifier (read-only) |
type | ElementType | Element type string (read-only) |
mounted | boolean | Whether the element is currently mounted (read-only) |
Events:
| Event | Fires when… |
|---|---|
ready | The iframe has loaded and the element is interactive |
change | The user types or the value changes |
focus | The input gains focus |
blur | The input loses focus |
error | An infrastructure or API error occurs |
See Events for full payload shapes.
card
Collects the card number, expiration date, and CVV together in a single iframe. Use it when you want one compact, pre-styled card input instead of mounting cardNumber, expiry, and cvv separately. The three sub-fields are exposed directly on the element — card.number, card.expiryDate, and card.cvc — for tokenization.
- Web Elements
- React Elements
const cardEl = bt.createElement('card', {
placeholder: {
cardNumber: '4242 4242 4242 4242',
expiryDate: 'MM/YY',
cvc: 'CVC',
},
layout: 'auto',
iconPosition: 'left',
});
await cardEl.mount('#card-container');
import { useRef } from 'react';
import { CardElement } from '@basis-theory/react-elements';
const cardRef = useRef(null);
<CardElement
ref={cardRef}
placeholder={{ cardNumber: '4242 4242 4242 4242', expiryDate: 'MM/YY', cvc: 'CVC' }}
layout="auto"
iconPosition="left"
onChange={(event) => console.log(event.detail.isValid)}
/>
card options
| Option | Type | Default | Description |
|---|---|---|---|
placeholder | { cardNumber?: string; expiryDate?: string; cvc?: string } | — | Placeholder text per sub-field. Unlike other elements, this is a structured object, not a string. |
ariaLabel | string | 'Card Input' | ARIA label for the card input |
disabled | boolean | false | Disables all three sub-fields |
readOnly | boolean | false | Makes all three sub-fields read-only |
layout | 'row' | 'column' | 'auto' | 'auto' | Field arrangement. 'auto' stacks the fields vertically when the container is narrower than stackAt |
stackAt | number | 400 | Container width (px) below which 'auto' layout stacks vertically. Only applies when layout: 'auto' |
iconPosition | 'left' | 'right' | 'none' | 'left' | Position of the card brand icon, or 'none' to hide it |
cardBrands | string[] | — | Restricts the card brands the input will accept |
binLookup | boolean | { enabled?: boolean; debounceMs?: number } | — | Enables BIN enrichment on the card number field (brand, funding, issuer metadata). Setting coBadge overrides this — BIN lookup stays on even with binLookup: false |
coBadge | { preferredNetworks?: string[]; mode?: 'auto' | 'manual' } | — | Co-badged card network support. Forces binLookup on, overriding an explicit binLookup: false |
Sub-field accessors
The card element renders three sub-fields inside one iframe. To tokenize, reference these sub-fields directly on the element rather than passing the card element itself:
| Accessor | Sub-field |
|---|---|
card.number | Card number |
card.expiryDate | Expiration date (resolves both month and year) |
card.cvc | CVV |
These are sub-field references for tokenization only. They cannot be mounted, updated, or focused independently — call mount(), update(), focus(), etc. on the card element itself.
Tokenization
Pass the sub-field references into bt.tokens.create. Use card.expiryDate for both expiration_month and expiration_year — passing the same reference to both is required, because the SDK parses the MM/YY value into a numeric month and four-digit year inside the secure element before the request is sent:
const token = await bt.tokens.create({
type: 'card',
data: {
number: cardEl.number,
expiration_month: cardEl.expiryDate,
expiration_year: cardEl.expiryDate,
cvc: cardEl.cvc,
},
});
// Send token.id to your backend to charge the card
console.log('Token created:', token.id);
cardNumber
Collects the card PAN. Validates format and Luhn checksum, and detects the card brand as the user types.
- Web Elements
- React Elements
const cardNumberEl = bt.createElement('cardNumber', {
placeholder: '4242 4242 4242 4242',
ariaLabel: 'Card number',
});
await cardNumberEl.mount('#card-number-container');
import { useRef } from 'react';
import { CardNumberElement } from '@basis-theory/react-elements';
const cardNumberRef = useRef(null);
<CardNumberElement
ref={cardNumberRef}
placeholder="4242 4242 4242 4242"
ariaLabel="Card number"
onChange={(event) => console.log(event.detail.isValid)}
/>
change event
The cardNumber element emits additional fields on its change event beyond the shared ChangeEventDetail:
| Field | Type | Description |
|---|---|---|
cardBrand | string | Detected brand (e.g. 'visa', 'mastercard', 'amex') |
last4 | string | null | Last 4 digits — safe to display |
bin | string | null | BIN (first 6–8 digits) |
cvvLengths | number[] | null | Expected CVV length(s) for detected brand |
potentialBrands | string[] | All brands that could match at the current input length |
matchStrength | number | Brand detection confidence (0–1) |
networks | string[] | Networks a co-badged card supports. Present only with BIN lookup or co-badge enabled — see Co-Badge Support. |
selectedNetwork | string | Selected network on a co-badged card — see Co-Badge Support. |
cardNumberEl.on('change', (event) => {
const { isValid, cardBrand, last4, bin } = event.detail;
console.log(`${cardBrand} ...${last4}`, isValid);
});
Co-brand detection
Some card numbers match more than one brand (for example, a card that could be Mastercard or Maestro). The SDK detects this automatically and exposes potentialBrands on the change event. When potentialBrands.length > 1, you can prompt the user to pick their preferred brand:
cardNumberEl.on('change', (event) => {
const { cardBrand, potentialBrands, matchStrength } = event.detail;
if (potentialBrands && potentialBrands.length > 1) {
// Show brand picker UI with potentialBrands options
showBrandPicker(potentialBrands);
} else {
hideBrandPicker();
// cardBrand is the detected brand (e.g. 'mastercard')
}
});
matchStrength (0–1) indicates how confident the detection is — useful for deciding when to show or hide UI. A value approaching 1 means only one brand matches.
Co-badge and BIN lookup
cardNumber and card can enrich detection using the card's BIN and handle co-badged cards — cards that carry more than one payment network (for example a domestic network alongside Visa or Mastercard). Enabling this surfaces the card's networks and selectedNetwork on the change event and fires a networkChanged event when the selection changes.
This is configured with the binLookup and coBadge options and has its own guide: see Co-Badge Support.
React ref
CardNumberElementRef extends the base ref with convenience state:
| Property | Type | Description |
|---|---|---|
complete | boolean | Input is valid and complete |
empty | boolean | Input is empty |
valid | boolean | Input passes validation |
cardBrand | string | Detected card brand |
last4 | string | Last 4 digits |
bin | string | BIN |
expiry
Collects the card expiration date in MM/YY format. Validates that the date is not in the past.
- Web Elements
- React Elements
const expiryEl = bt.createElement('expiry', {
placeholder: 'MM/YY',
});
await expiryEl.mount('#expiry-container');
import { ExpiryElement } from '@basis-theory/react-elements';
<ExpiryElement
placeholder="MM/YY"
onChange={(event) => console.log(event.detail.isValid)}
/>
The expiry element accepts only the shared options. When used in tokenization, pass the same element reference for both expiration_month and expiration_year — passing the same reference to both is required, because the SDK parses the MM/YY value into a numeric month and four-digit year inside the secure element before the request is sent:
bt.tokens.create({
type: 'card',
data: {
number: cardNumberEl,
expiration_month: expiryEl,
expiration_year: expiryEl,
cvc: cvvEl,
},
});
cvv
Collects the card verification code (CVV/CVC). When a cardNumber element is present on the same page, the SDK automatically updates the CVV element's expected length to match the detected card brand (3 digits for Visa/Mastercard, 4 for Amex).
- Web Elements
- React Elements
const cvvEl = bt.createElement('cvv', {
placeholder: '•••',
showToggle: true, // renders a show/hide button inside the field
});
await cvvEl.mount('#cvv-container');
import { CVVElement } from '@basis-theory/react-elements';
<CVVElement
placeholder="•••"
onChange={(event) => console.log(event.detail.isValid)}
/>
CVV-specific options
| Option | Type | Default | Description |
|---|---|---|---|
showToggle | boolean | false | Renders a show/hide toggle inside the field. Web elements only. Set at creation — cannot be changed via update(). |
text
General-purpose single-line text input. Use it to collect any non-card sensitive value (SSN, account number, routing number, etc.) without it touching your servers.
- Web Elements
- React Elements
const ssnEl = bt.createElement('text', {
placeholder: '•••-••-••••',
mask: [/\d/, /\d/, /\d/, '-', /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/],
transform: [/-/g, ''], // strip dashes before tokenization
validation: /^\d{9}$/, // must be exactly 9 digits after transform
});
await ssnEl.mount('#ssn-container');
import { TextElement } from '@basis-theory/react-elements';
<TextElement
placeholder="•••-••-••••"
mask={[/\d/, /\d/, /\d/, '-', /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]}
transform={[/-/g, '']}
validation={/^\d{9}$/}
onChange={(event) => console.log(event.detail.isValid)}
/>
Text-specific options
| Option | Type | Mutable via update() | Description |
|---|---|---|---|
validation | RegExp | No | Pattern the value must match to be considered valid |
required | boolean | Yes | Whether the field is required |
maxLength | number | Yes | Maximum character length |
password | boolean | Yes | Renders as a password field (type="password") |
inputMode | string | Yes | Mobile keyboard hint ('numeric', 'tel', 'email', etc.) |
mask | (RegExp | string)[] | No | Character-by-character input mask. Each position is a RegExp that the typed character must match, or a literal string character inserted automatically |
transform | [RegExp, string] | No | Applied to the value before tokenization: value.replace(pattern, replacement). Does not affect what the user sees |
validation, mask, and transform define the data contract for the element and are immutable after creation. To change them, unmount and recreate the element.Mask example
// Phone: (555) 123-4567
const phoneEl = bt.createElement('text', {
placeholder: '(555) 123-4567',
mask: [
'(', /\d/, /\d/, /\d/, ')',
' ', /\d/, /\d/, /\d/,
'-', /\d/, /\d/, /\d/, /\d/,
],
transform: [/\D/g, ''], // strip non-digits → "5551234567" is tokenized
});
copyButton
Renders a secure button that copies a value to the clipboard — either a static string or the live value of another element (for example, mirroring a cardNumber). Unlike the input elements, copyButton has no validation and emits no change event; it emits a copy event when clicked.
copyButton requires allowClipboard: true. Omitting it throws a ConfigurationError. Copying raw card data to the clipboard may fall outside your PCI-DSS scope — only enable this once you have confirmed clipboard access is acceptable for the data being copied.- Web Elements
- React Elements
const copyButton = bt.createElement('copyButton', {
allowClipboard: true, // required — omitting throws ConfigurationError
text: 'Copy card number',
});
await copyButton.mount('#copy-button-container');
// Link the button to another element's live value
await copyButton.setValueRef(cardNumberEl);
copyButton.on('copy', (event) => {
if (event.detail.success) {
console.log('Copied to clipboard');
}
});
import { useRef } from 'react';
import { CardNumberElement, CopyButtonElement } from '@basis-theory/react-elements';
function CardWithCopy() {
const cardNumberRef = useRef(null);
const copyButtonRef = useRef(null);
// Link the copy button to the card number once both are ready
const linkCopyButton = () => {
if (cardNumberRef.current) {
copyButtonRef.current?.setValueRef(cardNumberRef.current);
}
};
return (
<>
<CardNumberElement ref={cardNumberRef} onReady={linkCopyButton} />
<CopyButtonElement
ref={copyButtonRef}
allowClipboard // required — omitting throws ConfigurationError
text="Copy card number"
onCopy={(event) => {
if (event.detail.success) console.log('Copied to clipboard');
}}
/>
</>
);
}
copyButton options
| Option | Type | Default | Description |
|---|---|---|---|
allowClipboard | true | — | Required. Explicit opt-in to clipboard access. Must be true — any other value (including omitting it) throws ConfigurationError. |
value | string | — | Static string to copy when the button is clicked. Overridden by setValueRef() if called. |
text | string | 'Copy' | Button label. |
disabled | boolean | false | Disables the button. |
copyButton does not accept the shared input options (placeholder, ariaLabel, readOnly).
setValueRef
setValueRef(element) links the copy button to another element's live value. After linking, clicking the button copies the current value of the referenced element — that value never crosses into your page. Use it to let a user copy a cardNumber (or any other element) they are viewing, without exposing the raw value to your application.
const cardNumberEl = bt.createElement('cardNumber');
const copyButton = bt.createElement('copyButton', { allowClipboard: true });
await Promise.all([
cardNumberEl.mount('#card-number'),
copyButton.mount('#copy-button'),
]);
await copyButton.setValueRef(cardNumberEl);
| Method | Signature | Description |
|---|---|---|
setValueRef | (element: Element) => Promise<void> | Links the button to another element's live value. A linked reference takes precedence over the static value option. Throws if the button is not mounted or the passed element has no ID. |
In React, call setValueRef on the element ref (see the example above).
copy event
copyButton emits a copy event each time it is clicked, reporting whether the copy succeeded. In React, subscribe with the onCopy prop. See Events for the full payload.
For visual customization across all elements (colors, typography, spacing, border radius), see Theming.