> ## Documentation Index
> Fetch the complete documentation index at: https://docs.overflow.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Compose standalone elements

> Lay out contact, address, and payment method elements yourself.

When Checkout's built-in layout does not fit, mount the individual elements and compose them into your own form. This guide shows the common pattern: an email input, a card element, and a submit button that gates on both.

For a single-mount all-in-one flow, use [Accept a payment](/payment-elements/guides/accept-a-payment) instead.

## Pattern

1. Mount every element you need (contact fields, address, payment method).
2. Gate an external submit control on each element's `onChange.complete`.
3. On submit, call `element.submit()` on the payment-method element and forward its `onSubmit` payload to your server.

## Example

```html theme={null}
<form id="pay-form">
  <div id="email"></div>
  <div id="card"></div>
  <div id="submit"></div>
</form>
```

```javascript theme={null}
import { loadOverflow } from '@getoverflow/payment-elements';

const overflow = await loadOverflow('live_pub_...');
if (!overflow) return; // SSR / non-browser

const email = overflow.email({ required: true });
const card = overflow.card();
const submit = overflow.submitButton({ label: 'Pay' });

let emailOk = false;
let cardOk = false;
const sync = () => submit.update({ disabled: !(emailOk && cardOk) });

email
  .on('onChange', ({ complete }) => { emailOk = complete; sync(); })
  .mount('#email');

card
  .on('onChange', ({ complete }) => { cardOk = complete; sync(); })
  .mount('#card');

submit
  .on('onSubmit', () => card.submit()) // relay: value is undefined
  .mount('#submit');

card.on('onSubmit', async ({ value }) => {
  await fetch('/your-backend/authorize', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: email.value,
      paymentMethod: value,
    }),
  });
});

card.on('onError', ({ code, message, fieldErrors }) => {
  if (code === 'validation_failed') paintFieldErrors(fieldErrors);
  else toast(message);
});
```

## Notes on `submitButton`

`submitButton` is a pure relay. It fires `onSubmit` with `value: undefined` on every click. Use it to drive an external `card.submit()` (or `bank.submit()`), or wire its `onSubmit` to your own submission logic. See the [submitButton reference](/payment-elements/elements/submit-button).

You do not have to use `submitButton`. A plain `<button>` outside the SDK works too:

```javascript theme={null}
document.querySelector('#pay').addEventListener('click', () => card.submit());
```

## Read values without waiting for events

Every element handle exposes a read-only `.value` getter that returns the current snapshot without waiting for an `onChange` fire. This is useful for stitching independent elements together (as in the example above, where `email.value` is read inside `card`'s `onSubmit` handler).

## Cross-links

* [Payment methods overview](/payment-elements/elements/payment-methods/overview)
* [Events overview](/payment-elements/events/overview)
* [Options: `required`](/payment-elements/options/required)
* [Options: `validate`](/payment-elements/options/validate)
