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

# Displaying an inline clickwrap

> Embed a clickwrap consent checkbox directly in your page using the ClickTerm SDK.

<Note>
  **Prerequisites:** [SDK v2.2.0+](/dev/sdk/web/installation) installed, a [published template](/product/getting-started/first-clickwrap) with an effective version, and your [App ID](/product/integrations/integrations-overview).
</Note>

Use `ClicktermDom.renderInline()` to embed a clickwrap agreement as a checkbox directly in your page. Unlike [dialog mode](/dev/guides/displaying-dialog-clickwrap) (which shows a modal overlay), inline mode gives you full control over where the checkbox appears and when the agreement is submitted.

<Frame caption="Inline clickwraps embedded in a registration form (Web SDK)">
  <img src="https://mintcdn.com/clickterm/L30sxVw6oeCI9QfT/dev/images/clickwrap-inline-subscribe-example-websdk.png?fit=max&auto=format&n=L30sxVw6oeCI9QfT&q=85&s=8112cdfef0105c3c6d5fb828f53b62a0" alt="An inline clickwrap consent checkbox embedded in a subscribe form (Web SDK)" width="442" height="708" data-path="dev/images/clickwrap-inline-subscribe-example-websdk.png" />
</Frame>

## Viewing the full agreement

The checkbox text includes a link to the agreement (for example, the template name or "Terms & Conditions"). When the end user clicks that link, the SDK opens a read-only dialog showing the full clickwrap content — without leaving your page or affecting the checkbox state. The user closes the dialog to return to your form.

<Frame caption="Clicking the link in the checkbox text opens the full agreement in a read-only dialog">
  <img src="https://mintcdn.com/clickterm/L30sxVw6oeCI9QfT/dev/images/clickwrap-inline-show-content.png?fit=max&auto=format&n=L30sxVw6oeCI9QfT&q=85&s=0a88836c41ceeb1ddb2a2cae0e32944f" alt="A read-only dialog showing the full clickwrap agreement content with a Close button, displayed over the dimmed registration form after the user clicked the agreement link in the inline checkbox" width="995" height="864" data-path="dev/images/clickwrap-inline-show-content.png" />
</Frame>

## Basic usage

```html theme={null}
<div id="my-consent"></div>
```

```javascript theme={null}
const { ClicktermClient, ClicktermDom } = window.Clickterm;
ClicktermClient.initialize('YOUR_CLICKTERM_APP_ID');

const handle = await ClicktermDom.renderInline(
  'my-consent',
  {
    endUserId: 'user-123',
    clickwrapTemplateId: 'YOUR_TEMPLATE_ID',
  }
);

// Finalize when the user submits your form
document.getElementById('my-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const result = await handle.finalize();
  console.log('Status:', result.status);
  console.log('Signature:', result.clicktermSignature);
  // Send result.clicktermSignature to your backend for verification
});
```

## Step-by-step

<Steps>
  <Step title="Prepare a container">
    Add an empty element with a unique `id` where the checkbox should appear:

    ```html theme={null}
    <div id="consent-checkbox"></div>
    ```

    The container must exist in the DOM when `renderInline()` is called and must not already have another inline clickwrap rendered in it. Each container holds one inline clickwrap at a time — to render into a container that already has one (for example, to re-render with different parameters), first call `handle.destroy()` on its handle to tear down the existing instance, otherwise `renderInline()` throws an "already rendered for container" error. After `destroy()`, the container is free to be reused.
  </Step>

  <Step title="Render the inline clickwrap">
    Call `ClicktermDom.renderInline()` with the container ID, request parameters, and optional callbacks:

    ```javascript theme={null}
    const handle = await ClicktermDom.renderInline(
      'consent-checkbox',
      {
        endUserId: 'user-123',
        clickwrapTemplateId: 'YOUR_TEMPLATE_ID',
        language: 'en',
        templatePlaceholders: { fullName: 'Alice Johnson' },
      },
      {
        onChange: (checked) => {
          document.getElementById('submit-btn').disabled = !checked;
        },
      }
    );
    ```

    The SDK fetches the agreement content, renders a checkbox + text inside a Shadow DOM in your container, and returns a handle.
  </Step>

  <Step title="Use the onChange callback">
    The `onChange` callback fires every time the checkbox state changes. Use it to enable or disable your submit button:

    ```javascript theme={null}
    onChange: (checked) => {
      submitButton.disabled = !checked;
    }
    ```
  </Step>

  <Step title="Finalize on form submit">
    When the user submits your form, call `finalize()` on the handle. This locks the checkbox, reads its state, and sends the confirmation to ClickTerm:

    ```javascript theme={null}
    const result = await handle.finalize();
    if (result.status === 'ERROR') {
      console.error('Failed:', result.error?.message);
      // Checkbox is automatically unlocked for retry
    } else {
      // Send result.clicktermSignature to your backend
    }
    ```
  </Step>

  <Step title="Verify on your backend">
    Send the Signature to your server and call the ClickTerm verification endpoint. This step is the same as dialog mode — see [Verifying a Signature](/dev/guides/verifying-signature) for full details.
  </Step>
</Steps>

## Parameters

| Parameter              | Type     | Required | Description                                                                                              |
| ---------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `containerId`          | `string` | Yes      | The `id` of the HTML element to render into                                                              |
| `endUserId`            | `string` | Yes      | Your identifier for the end user (max 256 chars)                                                         |
| `clickwrapTemplateId`  | `string` | Yes      | Template ID from the [ClickTerm dashboard](https://app.clickterm.com/templates)                          |
| `language`             | `string` | No       | Language code (e.g., `"en"`, `"de"`). See [supported languages](/product/reference/supported-languages)  |
| `templatePlaceholders` | `object` | No       | Structured [placeholder](/dev/guides/placeholders) object with standard fields plus `customPlaceholders` |

### Options

| Field       | Type                         | Description                                                                                               |
| ----------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| `onChange`  | `(checked: boolean) => void` | Called every time the checkbox state changes                                                              |
| `onLoading` | `(info) => void`             | Called before the agreement is requested — show your loader                                               |
| `onReady`   | `(info) => void`             | Called once the agreement is ready, whichever outcome applies                                             |
| `onError`   | `(error, info) => void`      | Called when rendering fails                                                                               |
| `style`     | `object`                     | Visual customization — see [ClicktermDom styling reference](/dev/sdk/web/clickterm-dom#styling-reference) |

<Note>
  `onLoading`, `onReady`, and `onError` were added in Web SDK `2.5.0`. Use them to
  reserve space and show a loader so your form does not jump when the agreement
  appears. See [inline lifecycle callbacks](/dev/sdk/web/clickterm-dom#inline-lifecycle-callbacks)
  for the payloads and outcome values.
</Note>

## Result format

The `finalize()` method returns a `ClicktermInlineFinalizeResult`:

| Field                | Type             | Description                                                                                                              |
| -------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `status`             | `string`         | `UNVERIFIED` — verify the Signature server-side. `ALREADY_ACCEPTED` — user already accepted. `ERROR` — submission failed |
| `clicktermSignature` | `string \| null` | The Signature to send to your backend for [verification](/dev/guides/verifying-signature). `null` if not available       |
| `submittedStatus`    | `string`         | What was sent: `ACCEPTED` (checked) or `DECLINED` (unchecked)                                                            |
| `error`              | `Error`          | Present only when `status` is `ERROR`                                                                                    |

<CodeGroup>
  ```json User accepts (checkbox checked) theme={null}
  {
    "status": "UNVERIFIED",
    "clicktermSignature": "eyJhbGciOiJSUzI1NiJ9...",
    "submittedStatus": "ACCEPTED"
  }
  ```

  ```json User declines (checkbox unchecked) theme={null}
  {
    "status": "UNVERIFIED",
    "clicktermSignature": "eyJhbGciOiJSUzI1NiJ9...",
    "submittedStatus": "DECLINED"
  }
  ```

  ```json User already accepted previously theme={null}
  {
    "status": "ALREADY_ACCEPTED",
    "clicktermSignature": null
  }
  ```

  ```json Submission error theme={null}
  {
    "status": "ERROR",
    "clicktermSignature": null,
    "error": { "message": "Network error" }
  }
  ```
</CodeGroup>

<Info>
  Requests to render an inline clickwrap are **not counted toward billing**. Only
  the [verification step](/dev/guides/verifying-signature) (`POST /clickwrap/verify`) is billed.
</Info>

## Handle types

Depending on the backend response, `renderInline()` may return different handle types:

### Normal case

The user has not accepted yet. A checkbox and agreement text are rendered in the container.

```javascript theme={null}
const handle = await ClicktermDom.renderInline('container', request);
handle.isChecked();        // false (initially unchecked)
handle.getCurrentResult(); // null (not yet finalized)
```

### Already accepted

The user already accepted this template in a previous session. **No UI is rendered** in the container.

```javascript theme={null}
const handle = await ClicktermDom.renderInline('container', request);
handle.isChecked();        // true (always)
handle.getCurrentResult(); // { status: 'ALREADY_ACCEPTED', clicktermSignature: null }
```

### Existing signature

The backend has a signature from a different flow. **No UI is rendered** in the container.

```javascript theme={null}
const handle = await ClicktermDom.renderInline('container', request);
handle.isChecked();        // false
handle.getCurrentResult(); // { status: 'UNVERIFIED', clicktermSignature: '...' }
```

### Handling all cases

```javascript theme={null}
const handle = await ClicktermDom.renderInline('container', request);
const initialResult = handle.getCurrentResult();

if (initialResult?.status === 'ALREADY_ACCEPTED') {
  // User already accepted — no checkbox shown, proceed with your flow
} else if (initialResult?.status === 'UNVERIFIED' && initialResult?.clicktermSignature) {
  // Existing signature — verify it server-side
} else {
  // Normal case — checkbox rendered, wait for user interaction
}
```

## Multiple inline clickwraps

You can render multiple independent inline clickwraps on the same page. Each needs its own container and `renderInline()` call.

```html theme={null}
<div id="consent-tos"></div>
<div id="consent-privacy"></div>
```

```javascript theme={null}
const tosHandle = await ClicktermDom.renderInline('consent-tos', {
  endUserId: 'user-123',
  clickwrapTemplateId: 'TEMPLATE_TOS',
});

const privacyHandle = await ClicktermDom.renderInline('consent-privacy', {
  endUserId: 'user-123',
  clickwrapTemplateId: 'TEMPLATE_PRIVACY',
});
```

### Finalizing all at once

Instead of calling `finalize()` on each handle, use `finalizeAll()`:

```javascript theme={null}
const results = await ClicktermDom.finalizeAll();

// Or finalize specific ones
const results = await ClicktermDom.finalizeAll(['consent-tos', 'consent-privacy']);

// Check results
for (const [containerId, result] of Object.entries(results)) {
  if (result.status === 'ERROR') {
    console.error(`Failed for ${containerId}:`, result.error?.message);
  } else {
    console.log(`${containerId} signature:`, result.clicktermSignature);
  }
}
```

## Error handling

Wrap `renderInline()` in try/catch. All errors are instances of `ClickwrapError`:

```javascript theme={null}
try {
  const handle = await ClicktermDom.renderInline('container', request, options);
} catch (error) {
  console.error('Failed to render:', error.message);
}
```

| Error                          | Cause                                                      | Fix                                                                 |
| ------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------- |
| Container element not found    | No element with that ID in the DOM                         | Ensure the element exists before calling `renderInline()`           |
| Already rendered for container | `renderInline()` called twice for the same container       | Call `handle.destroy()` first, or use a different container         |
| Missing inline content         | Backend returned content without the `inlineContent` field | Check your template configuration — make sure inline content is set |
| Client not initialized         | `ClicktermClient.initialize()` was not called              | Call `initialize()` before any render calls                         |

Finalize errors do **not** throw. They return a result with `status: 'ERROR'` and the checkbox is automatically unlocked for retry.

## Full working example

A registration form with two inline clickwraps:

```html theme={null}
<form id="registration-form">
  <label>
    Email
    <input type="email" id="email" required />
  </label>

  <label>
    Password
    <input type="password" id="password" required />
  </label>

  <!-- Inline clickwrap containers -->
  <div id="consent-tos"></div>
  <div id="consent-privacy"></div>

  <button type="submit" id="submit-btn" disabled>Create Account</button>
</form>

<!-- Load the SDK — must come before the inline script -->
<script src="https://unpkg.com/@clickterm/widget"></script>
<script>
  const { ClicktermClient, ClicktermDom } = window.Clickterm;
  ClicktermClient.initialize('YOUR_CLICKTERM_APP_ID');

  const submitBtn = document.getElementById('submit-btn');
  const checkboxStates = { tos: false, privacy: false };

  function updateSubmitButton() {
    submitBtn.disabled = !(checkboxStates.tos && checkboxStates.privacy);
  }

  async function init() {
    const tosHandle = await ClicktermDom.renderInline(
      'consent-tos',
      { endUserId: 'user-123', clickwrapTemplateId: 'TEMPLATE_TOS_ID' },
      {
        onChange: (checked) => { checkboxStates.tos = checked; updateSubmitButton(); },
        style: { checkbox: { color: '#2563EB', size: 18 } },
      }
    );

    const privacyHandle = await ClicktermDom.renderInline(
      'consent-privacy',
      { endUserId: 'user-123', clickwrapTemplateId: 'TEMPLATE_PRIVACY_ID' },
      {
        onChange: (checked) => { checkboxStates.privacy = checked; updateSubmitButton(); },
        style: { checkbox: { color: '#2563EB', size: 18 } },
      }
    );

    // Handle already-accepted cases
    if (tosHandle.getCurrentResult()?.status === 'ALREADY_ACCEPTED') {
      checkboxStates.tos = true;
    }
    if (privacyHandle.getCurrentResult()?.status === 'ALREADY_ACCEPTED') {
      checkboxStates.privacy = true;
    }
    updateSubmitButton();

    // Finalize on form submit
    document.getElementById('registration-form').addEventListener('submit', async (e) => {
      e.preventDefault();
      submitBtn.disabled = true;
      submitBtn.textContent = 'Creating account...';

      try {
        const results = await ClicktermDom.finalizeAll();
        const signatures = {};
        for (const [id, result] of Object.entries(results)) {
          if (result.status === 'ERROR') throw new Error(`Failed for ${id}`);
          signatures[id] = result.clicktermSignature;
        }
        // Send signatures + form data to your backend for verification
        console.log('Signatures:', signatures);
      } catch (error) {
        console.error('Registration failed:', error);
        submitBtn.disabled = false;
        submitBtn.textContent = 'Create Account';
      }
    });
  }

  init().catch(console.error);
</script>
```

## Next steps

<CardGroup cols={2}>
  <Card title="Verifying a Signature" icon="shield-check" iconType="light" href="/dev/guides/verifying-signature">
    Verify the user's action on your backend.
  </Card>

  <Card title="ClicktermDom API" icon="code" iconType="light" href="/dev/sdk/web/clickterm-dom">
    Full API reference, styling options, and type definitions.
  </Card>

  <Card title="Template placeholders" icon="brackets-curly" iconType="light" href="/dev/guides/placeholders">
    Pass dynamic data into the agreement text.
  </Card>

  <Card title="Dialog mode" icon="window-maximize" iconType="light" href="/dev/guides/displaying-dialog-clickwrap">
    Use the modal dialog approach instead.
  </Card>

  <Card title="Show accepted content" icon="file-check" iconType="light" href="/dev/guides/showing-accepted-content">
    Re-display an agreement the user already accepted.
  </Card>
</CardGroup>
