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

# Checkout Widget

> Embed a fully managed checkout button and modal for one or more products.

The Checkout Widget renders a buy-now button directly in your page. When clicked, it opens Henry's hosted checkout modal where the buyer completes payment, address, and order confirmation. Henry handles everything - you supply the product links and listen for events.

***

## Usage

```js theme={null}
import Henry from '@henrylabs/js';

const el = Henry.createCheckoutElement('#checkout-container', {
	widgetId: 'widget_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
	items: [
		{
			link: 'https://www.nike.com/t/air-max-270/AH8050-002',
			quantity: 1,
			variant: { size: '10', color: 'Black' },
		},
	],
});
```

Add a container in your HTML:

```html theme={null}
<div id="checkout-container"></div>
```

***

## Options

### `widgetId` `string` `*required`

Your Widget ID from the [Henry Dashboard](https://app.henrylabs.ai/dashboard/settings/app/developer).

***

### `items` `CartItem[]` `*required`

One or more products to include in the checkout. Each item requires a `link` (a direct product page URL) and optionally accepts:

| Field            | Type                                | Description                                              |
| ---------------- | ----------------------------------- | -------------------------------------------------------- |
| `link`           | `string`                            | Direct product URL                                       |
| `quantity`       | `number`                            | Number of units. Defaults to `1`                         |
| `variant`        | `string \| Record<string, unknown>` | Variant selection, e.g. `{ size: "10", color: "Black" }` |
| `shippingOption` | `{ id?: string; value?: string }`   | Pre-select a shipping option                             |
| `coupons`        | `string[]`                          | Coupon codes to apply                                    |
| `metadata`       | `Record<string, unknown>`           | Arbitrary metadata passed through to orders              |

```js theme={null}
items: [
	{
		link: 'https://www.nike.com/t/air-max-270/AH8050-002',
		quantity: 2,
		variant: { size: '10', color: 'Black' },
		coupons: ['SAVE10'],
		metadata: { source: 'product-page' },
	},
];
```

***

### `display` `object`

Controls how the widget is rendered.

#### `display.size`

| Value           | Description                                                 |
| --------------- | ----------------------------------------------------------- |
| `"full"`        | Full-width button                                           |
| `"comfortable"` | Standard-sized button with logo and label                   |
| `"compact"`     | Smaller button, good for tight layouts                      |
| `"bubble"`      | Floating circular button                                    |
| `"direct"`      | Opens the checkout iframe directly without a button trigger |

```js theme={null}
display: {
	size: 'compact';
}
```

#### `display.theme`

| Value      | Description                      |
| ---------- | -------------------------------- |
| `"light"`  | Light color scheme               |
| `"dark"`   | Dark color scheme                |
| `"system"` | Follows the user's OS preference |

```js theme={null}
display: {
	theme: 'system';
}
```

***

### `settings` `CartSettings`

Optional settings that control checkout behavior, service fees, and event triggers.

#### `settings.options`

| Field                  | Type                                | Description                                                    |
| ---------------------- | ----------------------------------- | -------------------------------------------------------------- |
| `allowPartialPurchase` | `boolean`                           | Whether the buyer can remove items and check out with a subset |
| `collectBuyerEmail`    | `"off" \| "required" \| "optional"` | Controls email collection                                      |
| `collectBuyerAddress`  | `"off" \| "required" \| "optional"` | Controls address collection                                    |
| `collectBuyerPhone`    | `"off" \| "required" \| "optional"` | Controls phone collection                                      |

```js theme={null}
settings: {
  options: {
    allowPartialPurchase: true,
    collectBuyerEmail: "required",
    collectBuyerAddress: "optional",
  },
}
```

#### `settings.serviceFeeFixed`

Add a fixed service fee to every checkout.

```js theme={null}
settings: {
  serviceFeeFixed: { value: 2.99, currency: "USD" },
}
```

#### `settings.serviceFeePercent`

Add a percentage-based service fee (0–100).

```js theme={null}
settings: {
  serviceFeePercent: 5,
}
```

#### `settings.events`

Fire webhooks or send emails on order state changes.

```js theme={null}
settings: {
  events: [
    {
      type: "order.purchase.full.complete",
      data: [{ type: "send_webhook", webhookUUID: "..." }],
    },
  ],
}
```

***

## Events

Use `.on()` to subscribe to checkout events and `.off()` to remove a listener.

### `checkout-complete`

Fires when the buyer successfully completes checkout.

```js theme={null}
el.on('checkout-complete', ({ status, order }) => {
	console.log('Status:', status);
	console.log('Order ID:', order?.id);
});
```

| Field    | Type                 | Description               |
| -------- | -------------------- | ------------------------- |
| `status` | `string`             | Final order status        |
| `order`  | `Order \| undefined` | Order object if available |

### `checkout-closed`

Fires when the checkout modal closes, either by the buyer or programmatically.

```js theme={null}
el.on('checkout-closed', ({ source }) => {
	console.log('Closed by:', source); // "user" | "system"
});
```

***

## Methods

### `.on(event, callback)` → `this`

Subscribe to a checkout event. Returns the element instance for chaining.

```js theme={null}
el.on('checkout-complete', handler);
```

### `.off(event, callback)` → `this`

Remove a previously registered listener.

```js theme={null}
el.off('checkout-complete', handler);
```

### `.destroy()`

Unmount the widget and clean up all event listeners. Call this when navigating away or unmounting a component.

```js theme={null}
el.destroy();
```

***

## TypeScript

```ts theme={null}
import type { CartItem, CheckoutOptions } from '@henrylabs/js';

const options: CheckoutOptions = {
	widgetId: 'widget_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
	items: [{ link: 'https://www.nike.com/t/air-max-270/AH8050-002', quantity: 1 }],
	display: { size: 'compact', theme: 'dark' },
};
```

***

## React

Use a `ref` guard to prevent double-mounting in Strict Mode:

```tsx theme={null}
import { useEffect, useRef } from 'react';
import Henry from '@henrylabs/js';

function BuyNow({ productUrl }: { productUrl: string }) {
	const initialized = useRef(false);

	useEffect(() => {
		if (initialized.current) return;
		initialized.current = true;

		const el = Henry.createCheckoutElement('#checkout-container', {
			widgetId: 'widget_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
			items: [{ link: productUrl, quantity: 1 }],
			display: { size: 'comfortable', theme: 'system' },
		});

		el.on('checkout-complete', ({ status, order }) => {
			console.log('Order placed:', order?.id, 'Status:', status);
		});

		return () => el.destroy();
	}, [productUrl]);

	return <div id='checkout-container' />;
}
```

***

## Related

<CardGroup cols={2}>
  <Card title="Card Element" icon="credit-card" href="/v1/sdk/client/elements/card-element">
    Embed a secure card input for custom payment flows
  </Card>

  <Card title="Server SDK - Checkout" icon="server" href="/v1/sdk/server/guides/checkout">
    Build headless checkout flows server-side
  </Card>
</CardGroup>
