Render modes: embed vs elements
This is the one home for the render-mode model. Embedded Fields renders in one of two modes, chosen per session via the integrationMode field when you create the session. Every page that mentions a mode states its assumption in one sentence and links here for the full model; read this once and the rest of the docs make sense.
embed(the default) — a monolith: one combined card iframe renders the whole payment surface together (card number + expiry + CVC, plus cardholder name, billing address, the payment-method picker, a returning buyer's saved cards, and any eligible Apple Pay / Google Pay buttons). You place onlyemailandsave-for-future-useyourself.elements— composable: you mount the card, email, cardholder, address, and the other pieces as discrete elements you place yourself, where your account supports it. For the card you keep the single combined box or split it into the three discretecard-number/card-expiry/card-cvcsecure fields.
The choice is per session — the same account can create one session in embed and the next in elements, with no account-level setting that locks you to one mode.
elements falls back to embed silently — it never errorsDiscrete rendering applies only where your account supports it. When it's unavailable, an integrationMode: "elements" session silently renders as embed — separately-mounted cardholder / address simply collapse back into the card iframe. Read the integrationMode echoed on the created session (and the capability map from vora.sessions.retrieve()) to confirm which surface you actually got, then code both layouts defensively. See Custom checkout with Elements for the create-session call.
Embedded Fields renders as one combined surface by default (integrationMode: "embed"), and the Element reference documents that default surface completely. Read on only if you create a session with integrationMode: "elements" to render the payment surface as discrete, separately-placed fields.
How the SDK reports it — element render modes
vora.sessions.retrieve(sessionId) returns a capability map. Each element renders in one of three modes for the active session:
- NATIVE — the gateway ships a UI primitive for this element and the SDK wraps it (one iframe; the gateway owns internal layout; your
styleoption translates to the gateway's theme API). - PROXY — the SDK renders the DOM itself, no iframe; your CSS has full control. Identical everywhere.
- MONOLITH — the main embed iframe already contains this element's surface. There's no separate mountable slot — the element returns
nullat create time.
Your code never has to name or detect the underlying vendor — the capability map is the contract.
The two rendering modes (today)
| Mode | How you set it | Card-field shape | What you place yourself |
|---|---|---|---|
| embed (default) | integrationMode: "embed" (or omit) | One combined card box — number + expiry + CVC together, plus cardholder, billing address, wallets and the payment-method picker — all inside a single iframe | Only email and save-for-future-use are separate elements you place |
| elements (where supported) | integrationMode: "elements" | Either one combined card box (number + expiry + CVC together) or the three discrete secure fields card-number / card-expiry / card-cvc placed independently | You place card (or the three discrete card fields), email, cardholder, address, save-for-future-use and payment-method-picker as separate elements |
Splitting the card into separate fields. In
elementsmode you can render the card as the combined box or as the three discrete secure fields —card-number,card-expiry, andcard-cvc— each mounted into its own container (they share one secure-fields controller and tokenize together via onesubmit()). See Discrete card fields. On a binder without discrete-field support, a discrete field's.mount()throwsframe_unsupported_element— use the combinedcardelement instead. Inembedmode the card is always the one combined box.
Capability matrix
| Element | embed (default) | elements |
|---|---|---|
card | MONOLITH (one iframe contains the entire checkout) | NATIVE (one combined number/expiry/CVC card iframe) |
card-number / card-expiry / card-cvc | (not applicable — card is one combined box) | NATIVE (three discrete secure fields, mounted independently) |
cardholder | MONOLITH (inside the main card iframe) | PROXY (SDK-rendered input) |
email | PROXY | PROXY |
address (billing) | MONOLITH (inside the main iframe) | NATIVE (account-shipped address iframe) |
payment-method-picker | MONOLITH | PROXY (SDK-rendered button row) |
save-for-future-use | PROXY (consent surface is always SDK-rendered for compliance) | PROXY |
saved-methods | MONOLITH | NATIVE / PROXY (not usable yet) |
Wallet buttons (Apple Pay / Google Pay) render inside the
cardmount in every mode when the buyer's device and your domain are eligible — see Element reference → Apple Pay & Google Pay. There's no separate row element to position.
The per-element options (cardholder, address, picker, etc.) and their collect shapes live in the Element reference — this page is only the render-mode model.
What each mode means in practice
- embed (default) — you mount
cardand that's effectively your whole payment surface. Its iframe contains the card fields, wallets, picker, and cardholder.emailandsave-for-future-useare still separate PROXY elements you can place and style freely; the other elements either returnnullor render inside the monolith. This is the path the Element reference documents. - elements (where supported) — request
integrationMode: "elements"per session and the SDK renders elements discretely where your account supports it (otherwise the session silently falls back to the defaultembed— never an error). You placecard,email,cardholder,address,save-for-future-use, andpayment-method-pickeras independently-positioned elements, with full outer-styling control on the SDK-rendered (PROXY) ones. (The discretesaved-methodspicker is not usable yet — the element ships in the SDK but returns no cards, so a returning buyer's saved cards surface inside theembedmonolith, not as a discrete element.) For the card you have a choice: the combinedcardbox (number + expiry + CVC together) or the three discrete secure fieldscard-number/card-expiry/card-cvc, each mounted independently — see Discrete card fields. Wallet buttons render alongside the card inputs inside thecardmount.
Composing multiple elements
With integrationMode: "elements", mount each element where you want it and submit the collection as a unit:
const elements = vora.elements.create({
theme: { font: { size: "16px" }, color: { text: "#1a1a1a" } },
});
const card = elements.create("card", {});
const address = elements.create("address", { mode: "billing" });
const email = elements.create("email", {});
card.mount("#card-element");
address.mount("#address-element");
email.mount("#email-element");
// One submit() collects from every mounted element, drives tokenization,
// and returns a single flat SubmitResult — branch error -> token -> charged.
const result = await elements.submit();
The elements collection shares theme defaults across child elements. With the default integrationMode: "embed" the same code is valid — but address and cardholder render inside the card iframe rather than at their own mount points, so mounting them separately has no visible effect.
The submit result is one flat SubmitResult object regardless of how many elements you mount — every field optional, discriminated at runtime in the order error → token → charged. The canonical interface and the full three-outcome contract live in one place: Element reference → SubmitResult shape. Per-element options (cardholder, address, styling) live in the Element reference; the styling model — what the style option themes vs what you style on your own DOM — lives in Customize the look & feel.
What's next
- Element reference — the default
embedpath, per-element options, and theSubmitResultshape - Custom checkout with Elements — the elements-mode create-session walkthrough
- Customize the look & feel — the styling model (inner-iframe vs outer-DOM), themes, and playground
- Quickstart — end-to-end integration walkthrough
- Error handling —
VoraMirrorErrorcodes and recovery