Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 71 additions & 19 deletions platforms/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ present the world's highest converting, customizable, one-page checkout. The
presented experience is a fully-featured checkout that preserves all of the
store customizations: Checkout UI extensions, Functions, branding, and more. It
also provides web idiomatic defaults such as opening checkout in a popup or
new tab, a transient overlay scrim while the popup is open, and convenient
developer APIs to embed, customize, and follow the lifecycle of the checkout
experience via the
new tab, a transient overlay scrim while the popup is open, and
convenient developer APIs to embed, customize, and follow the lifecycle of the
checkout experience via the
[Embedded Checkout Protocol](https://ucp.dev/2026-04-08/specification/embedded-checkout/).

Check out our blog to
Expand All @@ -33,6 +33,8 @@ Check out our blog to
- [Usage with the Shopify Storefront API](#usage-with-the-shopify-storefront-api)
- [Configuration](#configuration)
- [`src`](#src)
- [`presentation`](#presentation)
- [Authentication](#authentication)
- [`target`](#target)
- [`debug`](#debug)
- [Popup dimensions](#popup-dimensions)
Expand Down Expand Up @@ -97,7 +99,7 @@ checkout:
<shopify-checkout
id="checkout"
src="https://your-store.myshopify.com/checkouts/cn/abc123"
target="popup"
presentation="popup"
></shopify-checkout>

<button id="buy-now">Buy now</button>
Expand All @@ -112,9 +114,9 @@ checkout:
</script>
```

The element has no visible layout of its own beyond a transient `<dialog>`
scrim that appears over the host page while the popup is open. It can sit
anywhere in your DOM.
For popup and auto presentations, the element has no visible layout of its own
beyond a transient `<dialog>` scrim that appears over the host page while the
popup is open.

See [usage with the Storefront API](#usage-with-the-shopify-storefront-api)
below for details on how to obtain a checkout URL.
Expand All @@ -129,7 +131,7 @@ import type {ShopifyCheckout} from '@shopify/checkout-kit';

const checkout = document.createElement('shopify-checkout') as ShopifyCheckout;
checkout.src = 'https://your-store.myshopify.com/checkouts/cn/abc123';
checkout.target = 'popup';
checkout.presentation = 'popup';
document.body.append(checkout);

checkout.addEventListener('ec.complete', (event) => {
Expand Down Expand Up @@ -192,7 +194,7 @@ export function BuyNowButton({checkoutUrl}: {checkoutUrl: string}) {

return (
<>
<shopify-checkout ref={checkoutRef} src={checkoutUrl} target="popup" />
<shopify-checkout ref={checkoutRef} src={checkoutUrl} presentation="popup" />
<button onClick={() => checkoutRef.current?.open()}>Buy now</button>
</>
);
Expand All @@ -216,7 +218,12 @@ declare module 'react' {
'shopify-checkout': DetailedHTMLProps<
HTMLAttributes<ShopifyCheckout>,
ShopifyCheckout
> & {src?: string; target?: string; debug?: boolean};
> & {
src?: string;
presentation?: string;
target?: string;
debug?: boolean;
};
}
}
}
Expand Down Expand Up @@ -323,19 +330,64 @@ checkout: `ec_version` (Embedded Checkout Protocol version),
`ec_delegate` (which capabilities the host delegates), and `ck_version`
(the Checkout Kit version).

### `presentation`

How checkout is presented when `open()` is called. Defaults to `"auto"`.

| Value | Behavior |
| ---------- | --------------------------------------------------------------------- |
| `"auto"` | Opens checkout in a new browser tab or named window based on `target`. |
| `"popup"` | Opens checkout in a popup window sized and centered over the page. |

For popup integrations, prefer `presentation="popup"`:

```html
<shopify-checkout
src="https://your-store.myshopify.com/checkouts/cn/abc123"
presentation="popup"
/>
```

```ts
checkout.presentation = 'popup';
checkout.open();
```

For new-tab integrations, use `presentation="auto"` with `target="_blank"`.
For compatibility, omitting `presentation` and setting `target="popup"` still
opens a popup, but prefer `presentation="popup"` for new code:

```html
<shopify-checkout
src="https://your-store.myshopify.com/checkouts/cn/abc123"
presentation="auto"
target="_blank"
/>
```

### Authentication

Authentication for embedded checkout is not available in Checkout Kit yet.
[Authenticated checkout](https://ucp.dev/latest/specification/embedded-checkout/#authentication)
via UCP flows will be documented once they are supported.

### `target`

Where the checkout is presented. Defaults to `"auto"`.
The browser context target used when `presentation="auto"`. Defaults to
`"auto"`.

| Value | Behavior |
| ---------- | ------------------------------------------------------------------- |
| `"auto"` | Opens checkout in a new browser tab (default). |
| `"popup"` | Opens checkout in a popup window sized and centered over the page. |
| `"_blank"` | Synonym for `"auto"` — new tab. |
| _(string)_ | Any other value is treated as a named window target, the same as the [`target` parameter of `window.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open#target). |
| `"popup"` | Legacy alias for `presentation="popup"` only when `presentation` is omitted. |
| `"_blank"` | Opens checkout in a new browser tab/window. |
| _(string)_ | Passed as the [`target` parameter of `window.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open#target). |

When `presentation="auto"` is set explicitly, `target` is passed to
`window.open()` as the browser context target.

```html
<shopify-checkout target="popup" />
<shopify-checkout presentation="auto" target="_blank" />
```

> [!NOTE]
Expand All @@ -354,9 +406,9 @@ and event handlers; turn it off in production.

### Popup dimensions

When `target="popup"`, the popup is centered over the host window. Defaults
are `600 × 600`, capped at 90% of the host window. Override via CSS custom
properties:
When `presentation="popup"`, the popup is centered over the host window.
Defaults are `600 × 600`, capped at 90% of the host window. Override via CSS
custom properties:

```css
shopify-checkout {
Expand Down Expand Up @@ -394,7 +446,7 @@ exactly the fields relevant to that moment.
| `ec.complete` | `{checkout, order}` | The buyer completed the order successfully. |
| `ec.close` | _(none)_ | The popup was dismissed (by the buyer, by `close()`, or by `focus` loss). |
| `ec.error` | `{error}` | Session-level fatal error — tear down the embedded context. |
| `ec.line_items.change` | `{checkout, lineItems}` | The cart's line items changed (item added/removed/quantity updated). |
| `ec.line_items.change` | `{checkout, lineItems}` | The cart's line items changed (item added/removed/quantity updated). |
| `ec.totals.change` | `{checkout, totals}` | The cart totals changed (subtotal, tax, shipping, discounts, total). |
| `ec.messages.change` | `{checkout, messages}` | Checkout-level warnings/errors/info shown inside the checkout changed. |

Expand Down
13 changes: 7 additions & 6 deletions platforms/web/sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ pnpm sample

Vite serves at `http://localhost:5173`. The page has three panels:

- **Options** — form for `src`, `target` (`auto` | `popup`), and `debug`,
plus buttons for `open()`, `close()`, and `focus()`.
- **Options** — form for `src`, `presentation`, `target`, and `debug`, plus
buttons for `open()`, `close()`, and `focus()`.
- **Demo Storefront** — a mocked product card with **Buy now** calling
`checkout.open()`. The button stays disabled until a checkout URL is set.
The collapsible readout shows `checkout`, `error`, `target`, and `debug`.
`checkout.open()`. The button stays disabled until a checkout URL is set. The
collapsible readout shows `checkout`, `error`, `presentation`, `target`, and
`debug`.
- **Events** — log of dispatched events with a JSON snapshot of state at fire
time.

The element is mounted on `<body>`. For `popup` / `auto`, the visible UI is
mostly the overlay scrim while checkout is open in a separate window or tab.
Popup / auto presentations mostly show the overlay scrim while checkout is open
in a separate window or tab.

## Build

Expand Down
16 changes: 14 additions & 2 deletions platforms/web/sample/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,19 @@ <h2 id="options-heading">Options</h2>
/>
</label>

<label>
<span>presentation</span>
<select name="presentation">
<option value="popup" selected>popup</option>
<option value="auto">auto (new tab)</option>
</select>
</label>

<label>
<span>target</span>
<select name="target">
<option value="popup" selected>popup (window)</option>
<option value="auto">auto (new tab)</option>
<option value="_blank" selected>_blank</option>
<option value="checkout-window">checkout-window</option>
</select>
</label>

Expand Down Expand Up @@ -111,13 +119,17 @@ <h3 class="product-title">Studio Plant Pot</h3>
</div>
</article>

<div id="checkout-host" class="checkout-host"></div>

<details id="component-state">
<summary>Component state</summary>
<dl>
<dt>checkout</dt>
<dd id="state-checkout">—</dd>
<dt>error</dt>
<dd id="state-error">—</dd>
<dt>presentation</dt>
<dd id="state-presentation">—</dd>
<dt>target</dt>
<dd id="state-target">—</dd>
<dt>debug</dt>
Expand Down
14 changes: 9 additions & 5 deletions platforms/web/sample/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,24 @@ const eventLog = $<HTMLUListElement>("#event-log");
const clearLogButton = $<HTMLButtonElement>("#clear-log");
const buyNowButton = $<HTMLButtonElement>("#buy-now");
const buyHint = $<HTMLParagraphElement>("#buy-hint");
const checkoutHost = $<HTMLElement>("#checkout-host");

const stateNodes = {
checkout: $<HTMLElement>("#state-checkout"),
error: $<HTMLElement>("#state-error"),
presentation: $<HTMLElement>("#state-presentation"),
target: $<HTMLElement>("#state-target"),
debug: $<HTMLElement>("#state-debug"),
};

// ───── Mount the component (off-layout) ───────────────────────────────────
//
// In real merchant integrations the <shopify-checkout> element lives wherever
// it makes sense in the page. For popup / auto targets it has no visible UI
// of its own beyond a transient dialog scrim that appears when open() is
// called, so we attach it to <body> and leave the storefront panel free for
// the merchant's product UI.
// it makes sense in the page. Popup / auto presentations have no visible UI of
// their own beyond the transient dialog scrim.

const checkout = document.createElement("shopify-checkout") as ShopifyCheckout;
document.body.append(checkout);
checkoutHost.append(checkout);

const checkoutEl: HTMLElement = checkout;

Expand All @@ -70,6 +70,7 @@ function setStringAttribute(el: HTMLElement, name: string, value: FormDataEntryV
function syncAttributes(): void {
const data = new FormData(form);
setStringAttribute(checkout, "src", data.get("src"));
setStringAttribute(checkout, "presentation", data.get("presentation"));
setStringAttribute(checkout, "target", data.get("target"));
if (data.has("debug")) {
checkout.setAttribute("debug", "");
Expand All @@ -78,6 +79,7 @@ function syncAttributes(): void {
}

refreshBuyButton(data.get("src"));
refreshState();
}

function refreshBuyButton(src: FormDataEntryValue | null): void {
Expand Down Expand Up @@ -153,6 +155,7 @@ function snapshotState(): Record<string, unknown> {
return {
checkout: checkout.checkout,
error: checkout.error,
presentation: checkout.presentation,
target: checkout.target,
debug: checkout.debug,
};
Expand All @@ -161,6 +164,7 @@ function snapshotState(): Record<string, unknown> {
function refreshState(): void {
stateNodes.checkout.textContent = formatValue(checkout.checkout);
stateNodes.error.textContent = formatValue(checkout.error);
stateNodes.presentation.textContent = formatValue(checkout.presentation);
stateNodes.target.textContent = formatValue(checkout.target);
stateNodes.debug.textContent = formatValue(checkout.debug);
}
Expand Down
9 changes: 9 additions & 0 deletions platforms/web/sample/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ button:disabled {
box-shadow: var(--shadow-md);
}

.checkout-host {
width: 100%;
}

.checkout-host shopify-checkout {
display: block;
width: 100%;
}

.product-image {
aspect-ratio: 4 / 3;
display: grid;
Expand Down
14 changes: 14 additions & 0 deletions platforms/web/src/checkout.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ export const STYLES: SafeMarkup = css`
border: none;
}

#checkout-frame {
display: block;
inline-size: 100%;
block-size: 100%;
border: 0;
background: hsl(0, 0%, 100%);
}

:host {
@media (prefers-reduced-motion: reduce) {
--shopify-checkout-overlay-transition-duration: 1ms;
Expand Down Expand Up @@ -122,4 +130,10 @@ export const STYLES: SafeMarkup = css`
fill: currentColor;
}
}

:host([presentation="iframe"]) {
display: block;
block-size: var(--shopify-checkout-iframe-height, 640px);
min-block-size: var(--shopify-checkout-iframe-min-height, 640px);
}
`;
Loading
Loading