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

# Stable Attributes & Interactions

> Eight stable HTML attributes and the browser interaction signal for checkout controls.

Checkout hooks identify fields and controls in WeGive's stepper and single-page checkouts. You can use them with GTM, Tealium, or another service that runs JavaScript inside the checkout page. Hooks do not send analytics events by themselves.

## Identify a control

Find an element with `data-checkout-control`. The attribute can be on the input itself or its containing control. From a native event target, use `closest('[data-checkout-control]')`.

| Attribute | Meaning |
| - | - |
| `data-checkout-version` | Public hook contract version, currently `1`. |
| `data-checkout-control` | Semantic identity, such as `donor-email` or `address-city`. |
| `data-checkout-scope` | One mounted checkout instance within this document. It changes when that checkout is remounted. It is **not** a checkout-session UUID. |
| `data-checkout-id` | Checkout configuration ID; empty until known. |
| `data-checkout-portal` | `v1` or `v2`. |
| `data-checkout-layout` | `stepper` or `single-page`. |
| `data-checkout-context` | Optional distinction such as `mailing`, `billing`, or `custom-question`. |
| `data-checkout-instance` | Optional definition identity, such as a custom-question or designation ID. |

Use the scope, control, context, and instance together. Do not assume there is just one checkout, address, or question in a document. Custom-question radio/checkbox groups and verification-code inputs are compound fields: their native inputs share the field's identity.

Hooks remain associated with their checkout when a dropdown or dialog renders outside the checkout's visual container. Fields can appear and disappear as donors navigate, choose a payment method, or answer conditional questions. Install delegated listeners once instead of attaching a listener to every current input.

## Observe native inputs and buttons

Use a **capture listener on `window` in the checkout document**. Capture runs before handlers on the target can stop bubbling. For identity-only text interaction tracking, observe `input`; for leaving a field, observe `blur`; for native checkboxes/radios, observe `change`; for buttons, observe `click`.

```js theme={null}
const trackingListeners = new AbortController();

window.addEventListener('input', (event) => {
  if (!event.isTrusted) return; // Exclude formatter/model synchronization events.
  const field = event.target.closest?.('[data-checkout-control]');
  if (!field) return;

  const identity = {
    control: field.dataset.checkoutControl,
    scope: field.dataset.checkoutScope,
    context: field.dataset.checkoutContext ?? null,
    instance: field.dataset.checkoutInstance ?? null,
  };

  // Apply your own tracking rules and send the event to your chosen service.
  // This example intentionally does not read or copy the field's value.
  console.log('Checkout input', identity);
}, { capture: true, signal: trackingListeners.signal });

// When removing your tag/listener:
// trackingListeners.abort();
```

Native events retain normal browser semantics. For native edit events, filter `event.isTrusted` to exclude synthetic input events dispatched by formatters or model synchronization. Do not apply that filter to `wegive:checkout-interaction`, which is an explicit application signal. `input` can occur repeatedly while typing. Text `change` normally occurs when an edited value is committed; entering the existing value need not produce it. `focus` and `blur` describe focus movement, including programmatic focus, and are not proof of a donor edit. A click on a selector is not proof that a selection was made. Hidden transport inputs and internal search inputs are not donor-field edits.

When a blur listener needs the updated donor context, use **document capture** as in the [Tealium example](/external/onboarding/tracking/tealium). WeGive publishes the permitted donor edit during window capture, before document listeners. A window listener installed earlier could read the preceding value.

A script that intercepts events earlier on `window` with `stopImmediatePropagation()` can prevent subsequent listeners from observing them. Hooks do not override another script's event handling.

## Observe custom selections and hosted fields

Some controls update through component or provider callbacks rather than native `change`. For those controls, listen on `document` for `wegive:checkout-interaction`:

```js theme={null}
function handleCheckoutInteraction(event) {
  const detail = event.detail;
  if (detail?.version !== 1) return;
  if (detail.control === 'donor-business-type' && detail.action === 'selection') {
    // The donor committed a custom selection.
    console.log(detail);
  }
}
document.addEventListener('wegive:checkout-interaction', handleCheckoutInteraction);
```

The signal contains only `version`, `scope`, `portal`, `layout`, `checkout_id`, `control`, `context`, `instance`, and `action`. It contains no contact details, answers, passwords, payment values, or provider callback arguments.

In Portal V1, clearing a dropdown or erasing a searchable dropdown's selected value emits `clear`; choosing an option emits `selection`. Listen for both actions when tracking all selection changes. Clearing an address selector updates permitted donor context before the listener runs.

Use the catalog's documented observation path for each interaction. Do not count a custom selector's click and its `selection` signal as two selections. Initial defaults, data hydration, and model synchronization do not emit custom selection signals.

VGS card-number, expiration, and security-code fields live in provider iframes. Their supported `focus` and `blur` callbacks are bridged to this event. A blur requires a preceding focus; field initialization, reset, and teardown do not synthesize blur. Their contents and keystrokes are not exposed. See [VGS field event documentation](https://docs.verygoodsecurity.com/vault/developer-tools/vgs-collect/js/reference-documentation).

## Prompt visibility and first edit

`conversion-accept` and `conversion-decline` identify controls inside the conversion prompt in both portals/layouts. A hook's presence is not a click. To track an impression, scan for the prompt marker initially and observe dynamic insertion/visibility changes, checking that the marker is visible. Deduplicate per prompt display and checkout scope; observing both buttons must not count two impressions. WeGive does not emit a standard prompt-shown event. Customer-specific thresholds, labels, and impression rules remain yours.

The current tests prove the prompt controls are present, actionable, and removed on resolution; a complete customer impression-observer implementation has not been validated against a real Tealium profile.

There is no automatic first-dirty-field event. Customer code can track a first supported ordinary input edit, but VGS exposes only focus/blur here. An exact first edit anywhere, including inside a VGS frame, cannot be inferred from those signals. Focus or blur is not proof that its value changed.

## Embedded checkouts

Install the listener **inside the checkout iframe**, where the integration's JavaScript runs. A script running only in an unrelated parent page cannot observe DOM events from a cross-origin checkout iframe. Hooks do not automatically send interaction messages to the parent page.

## Compatibility

Hook version 1 names describe control meanings, independently of translations and visual styling. The catalog can grow to cover additional controls; each identity has a stable meaning. HTML IDs, CSS classes, and text are not the hook API. Hooks and standard analytics events have separate contracts.

## Example hook and bridge payload

An element may expose the following attributes. This is an illustrative DOM shape; the hook can be on the input or its wrapper.

```html theme={null}
<input
  data-checkout-version="1"
  data-checkout-control="address-city"
  data-checkout-scope="checkout-1"
  data-checkout-id="12"
  data-checkout-portal="v2"
  data-checkout-layout="single-page"
  data-checkout-context="mailing"
/>
```

The application bridge's `event.detail` has this shape (example: committed mailing-country selection):

```json theme={null}
{
  "version": 1,
  "scope": "checkout-1",
  "portal": "v2",
  "layout": "single-page",
  "checkout_id": 12,
  "control": "address-country",
  "context": "mailing",
  "instance": null,
  "action": "selection"
}
```

`version` is a number; `checkout_id` follows the source ID type or is null; `context` and `instance` are strings or null. HTML attributes themselves are strings. Supported bridge actions are `selection`, `clear`, `focus`, `blur`, and `approval`. Ordinary native `input`, `change`, and `click` are observed separately; WeGive does not turn every native event into this bridge. The bridge is a local browser event, not an automatic `utag.link` call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.