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

# @quickbutik/kit/react

> Generated type reference for the React entry point of @quickbutik/kit 1.8.0: provider, hooks and headless components.

<Info>
  Generated from the typings of `@quickbutik/kit@1.8.0` as published on npm. Do not edit this page by hand: run `npm run generate` in `tools/kit-api-reference`. For guided, example-led documentation see the [API reference](/kit/reference/client).
</Info>

```ts theme={null}
import { … } from "@quickbutik/kit/react"
```

## Classes

### CartStore

Observable cart state shared by every component that asks for it.

Lives outside React so a header badge and a cart page see the same object
without either owning the other — the store is the single writer, components
only ever subscribe. Built for `useSyncExternalStore`, hence the cached
snapshot: that hook compares snapshots by identity and would loop forever on
a freshly-allocated object.

#### Constructors

##### Constructor

```ts theme={null}
new CartStore(client, initialCart?): CartStore;
```

###### Parameters

| Parameter | Type |
| - | - |
| `client` | [`ShopkitClient`](/kit/api/core#shopkitclient) |
| `initialCart?` | [`Cart`](/kit/api/core#cart-1) \| `null` |

###### Returns

[`CartStore`](#cartstore)

#### Properties

##### clearCart()

```ts theme={null}
clearCart: () => Promise<void>;
```

Alias of [clear](#clear), under the name a "clear the cart" search finds.

###### Returns

`Promise`\<`void`>

##### getSnapshot()

```ts theme={null}
getSnapshot: () => CartSnapshot;
```

###### Returns

[`CartSnapshot`](#cartsnapshot)

##### subscribe()

```ts theme={null}
subscribe: (listener) => () => void;
```

###### Parameters

| Parameter | Type |
| - | - |
| `listener` | () => `void` |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

#### Methods

##### add()

```ts theme={null}
add(item): Promise<Cart | null>;
```

`cart.add` resolves (and creates) the cart itself, so this path never asks
the store for an id — that is what makes the very first "add to cart" a
single round trip instead of create-then-add.

###### Parameters

| Parameter | Type |
| - | - |
| `item` | [`AddCartItemInput`](/kit/api/core#addcartiteminput) |

###### Returns

`Promise`\<[`Cart`](/kit/api/core#cart-1) | `null`>

##### buyNow()

```ts theme={null}
buyNow(item, input): Promise<BuyNowResult>;
```

Add a product and start the checkout — `checkout.buyNow()` with the
store kept in step. Does NOT navigate; `useCheckout().buyNow` and
`<qb-buy-now>` do that with the `url` this resolves to.

Why through the store at all, when the shopper is about to leave the
page: they do not always leave. `no-redirect` keeps them here, a new-tab
handoff keeps this tab open, and the back button restores the page from
the back/forward cache exactly as it was — so a badge that missed the add
would read one item short for the rest of the visit. The cart the add
produced comes back on the result, so keeping the store right costs no
extra request.

Unlike the other mutations this one THROWS. A failed "buy now" is a
failed checkout, which the caller has to show (and `useCheckout` keeps in
its own `error`); stashing it on the cart snapshot would paint a cart
error for something the cart did not do. The add may have landed before
the handoff failed, though, so the cart is re-read on the way out —
quietly: a re-read that fails too keeps the snapshot as it was rather
than turning it into a cart error. Every failure is re-read, config
errors included: most of those (a missing `successUrl`, a campaign
mismatch) are thrown before anything is added, but a legacy handoff that
comes back without an order id or a link throws one AFTER the add. The
re-read is one quiet request on a failure path, and skipping it on the
wrong error would leave the badge one item short.

###### Parameters

| Parameter | Type |
| - | - |
| `item` | [`AddCartItemInput`](/kit/api/core#addcartiteminput) |
| `input` | [`StartCheckoutInput`](/kit/api/core#startcheckoutinput) |

###### Returns

`Promise`\<[`BuyNowResult`](/kit/api/core#buynowresult)>

##### clear()

```ts theme={null}
clear(): Promise<void>;
```

Empty the basket — delete the remembered cart and forget it. Goes through
`cart.clear()`, so a cart the server already dropped (404/410) still ends
up cleared here rather than stuck in `error` with an id that can never be
deleted.

A no-op, with no request and no revision bump, when there is nothing to
clear: a second click on "Töm varukorgen" is not a mutation.

###### Returns

`Promise`\<`void`>

##### hydrate()

```ts theme={null}
hydrate(cart): void;
```

Adopt a cart fetched elsewhere (an RSC passing its result down).

###### Parameters

| Parameter | Type |
| - | - |
| `cart` | [`Cart`](/kit/api/core#cart-1) \| `null` |

###### Returns

`void`

##### load()

```ts theme={null}
load(): Promise<Cart | null>;
```

Load the remembered cart if we have not already.

Never creates one: mounting a cart badge must not write a cookie or a row
for a visitor who has not added anything (crawlers included). Concurrent
callers share one request.

###### Returns

`Promise`\<[`Cart`](/kit/api/core#cart-1) | `null`>

##### onMutation()

```ts theme={null}
onMutation(listener): () => void;
```

Be told about every successful mutation, with the cart before and after.
Returns the unsubscribe function. See [CartMutationEvent](/kit/api/core#cartmutationevent).

###### Parameters

| Parameter | Type |
| - | - |
| `listener` | (`event`) => `void` |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

##### refresh()

```ts theme={null}
refresh(): Promise<Cart | null>;
```

Re-read from the server, bypassing the "already loaded" short-circuit.

###### Returns

`Promise`\<[`Cart`](/kit/api/core#cart-1) | `null`>

##### removeItem()

```ts theme={null}
removeItem(itemId): Promise<Cart | null>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `itemId` | `string` |

###### Returns

`Promise`\<[`Cart`](/kit/api/core#cart-1) | `null`>

##### updateItem()

```ts theme={null}
updateItem(itemId, quantity): Promise<Cart | null>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `itemId` | `string` |
| `quantity` | `number` |

###### Returns

`Promise`\<[`Cart`](/kit/api/core#cart-1) | `null`>

## Interfaces

### AnalyticsProviderProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="auto" /> `auto?` | `boolean` | Load the merchant's GA4 / GTM / Meta Pixel from the shop's tracking configuration (`GET /checkout/shop`). Default true. Set false when you configure every destination yourself. |
| <a id="buffersize" /> `bufferSize?` | `number` | How many events are remembered for destinations not ready for them yet. Default 50, minimum 1. |
| <a id="children" /> `children` | `ReactNode` | - |
| <a id="currency" /> `currency?` | `string` | The currency for events that carry none. Falls back to the cart's. |
| <a id="debug" /> `debug?` | `boolean` | Log every dispatch to the console. |
| <a id="destinations" /> `destinations?` | [`AnalyticsDestination`](/kit/api/core#analyticsdestination)\[] | Destinations of your own, on top of (or instead of) the shop's. The kit ships `googleTagDestination` and `metaPixelDestination`; anything else is one object implementing `AnalyticsDestination`. |
| <a id="loadbeforeconsent" /> `loadBeforeConsent?` | `boolean` | Load GTM / gtag.js before the shopper decides, in Consent Mode denied. Default false: nothing from Google loads until `analytics` is granted. Note that a GTM container loaded this way runs all of its tags. |
| <a id="requireconsent" /> `requireConsent?` | `boolean` | False runs analytics without consent — the hosted storefront's behaviour for a shop without the consent app, and what also makes the hosted checkout track ungated. Only for a storefront that is certain it owes its shoppers no consent. Default true: without a `<ConsentProvider>` nothing is sent, and one warning says so. |
| <a id="storeid" /> `storeId?` | `string` \| `number` \| `null` | The shop's numeric store id, for the purchase dedup key and Meta eventID. |

***

### AsyncState

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="data" /> `data` | `T` \| `null` | - |
| <a id="error" /> `error` | `Error` \| `null` | - |
| <a id="loading" /> `loading` | `boolean` | - |
| <a id="refetch" /> `refetch` | () => `void` | Re-run the fetch. Stable identity, safe as an effect dependency. |

***

### CartSnapshot

#### Extended by

* [`UseCartResult`](#usecartresult)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="cart" /> `cart` | [`Cart`](/kit/api/core#cart-1) \| `null` | - |
| <a id="error-1" /> `error` | `Error` \| `null` | - |
| <a id="itemcount" /> `itemCount` | `number` | Sum of item quantities; 0 when there is no cart. Convenience for badges. |
| <a id="pending" /> `pending` | `number` | Number of mutations in flight — drives an "updating…" affordance. |
| <a id="revision" /> `revision` | `number` | Bumped once per SUCCESSFUL mutation made through this store — `add`, `updateItem`, `removeItem`, `clear`, `buyNow` — and by nothing else. Not a change detector: `load`, `refresh` and `hydrate` leave it alone even when they bring back a different cart, and a failed mutation leaves it alone too. It answers exactly one question, "did this page just change the cart?", which is the question an embedded checkout has to be told about (see `EmbeddedCheckout.cartUpdated`) and a re-read from the server is not. Comparing it across snapshots is how a consumer notices; the absolute value means nothing. |
| <a id="status" /> `status` | [`CartStatus`](#cartstatus) | - |

***

### CheckoutProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="backurl" /> `backUrl?` | `string` | Where the checkout's "back to shop" links point. |
| <a id="classname" /> `className?` | `string` | - |
| <a id="confirmation" /> `confirmation?` | `"redirect"` \| `"inline"` | `"redirect"` (default) navigates to `successUrl` when the order completes; `"inline"` stays here and lets the frame render its own confirmation. |
| <a id="lang" /> `lang?` | `string` | Checkout UI language (`"sv"`, `"en"`). |
| <a id="minheight" /> `minHeight?` | `number` | Height before the frame reports its own, and the floor it never goes below. |
| <a id="oncomplete" /> `onComplete?` | (`detail`) => `void` | - |
| <a id="onempty" /> `onEmpty?` | () => `void` | There is nothing in the cart to check out, so no checkout is mounted. Called once when the cart settles empty — on load, or after the page empties it — the same moment `<qb-checkout>` emits `qb:checkout-empty`. Render your own "your cart is empty" from it; the component itself shows nothing but its container until items arrive. |
| <a id="onerror" /> `onError?` | (`detail`) => `void` | - |
| <a id="onevent" /> `onEvent?` | (`detail`) => `void` | GA4-shaped commerce events, for your own dataLayer or pixel. |
| <a id="onfallback" /> `onFallback?` | (`detail`) => `void` | The checkout is not being embedded — the shop cannot be, or the frame never answered (`reason: "handshake-timeout"`) — and the shopper is being sent to the hosted checkout at `detail.url` instead. |
| <a id="onready" /> `onReady?` | (`detail`) => `void` | - |
| <a id="onstep" /> `onStep?` | (`detail`) => `void` | - |
| <a id="style" /> `style?` | `CSSProperties` | - |
| <a id="successurl" /> `successUrl?` | `string` | Where the shopper lands after paying. Relative URLs resolve against this page, so `"/thanks"` works in every environment. |
| <a id="theme" /> `theme?` | `"light"` \| `"dark"` | Which theme the checkout is painted in. Set it when your own pages are dark, so the checkout does not flash white on the way in. Omitting it is NOT the same as passing `"light"`: omit it and the checkout follows the theme the merchant chose for their shop, and keeps following it even while the shopper has the checkout open; pass `"light"` and this checkout stays light whatever the merchant chose. Read once, with the rest of the session inputs — a page that flips its own dark mode while the shopper is standing in the checkout does not repaint the frame, because rebuilding the session would discard a payment they may be halfway through. Decide it before the component mounts. |

***

### ConsentBannerProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="children-1" /> `children?` | (`state`) => `ReactNode` | Your own markup, given the state. When present, NOTHING else renders — the default markup below is only for a banner with no children. |
| <a id="classname-1" /> `className?` | `string` | Extra class on the default markup's root. |
| <a id="labels" /> `labels?` | `Partial`\<[`ConsentLabels`](/kit/api/core#consentlabels)> \| `null` | Overrides for any of the built-in strings. |
| <a id="lang-1" /> `lang?` | `string` \| `null` | Language of the built-in copy (`"sv"`, `"en"`, `"da"`, `"nb"`, `"fi"`). Defaults to the provider's `lang`, then `<html lang>` in the browser, then English. Set it (here or on the provider) when rendering on the server, so both sides pick the same copy. |
| <a id="privacypolicyurl" /> `privacyPolicyUrl?` | `string` \| `null` | Adds a "Privacy policy" link to the description. |
| <a id="showwhendecided" /> `showWhenDecided?` | `boolean` | Keep rendering after a decision — for a cookie settings page, where the shopper should always be able to change their mind. Default false: the banner leaves once a decision exists and comes back on `openSettings()`. |
| <a id="unstyled" /> `unstyled?` | `boolean` | Leave out the built-in stylesheet. The default markup still renders with its `qb-consent*` class names (plus `qb-consent--unstyled`, which the built-in rules never match), for a site that styles every part itself. Default false. Without it, the rules are theme-able through `--qb-consent-*` custom properties and sit in `@layer qb-consent`, so any rule of the site's own wins. |

***

### ConsentBannerState

What the store exposes: the decision plus the one piece of UI state a banner
and a far-away "Cookie settings" link have to share — whether the
preferences panel is open. Not persisted.

#### Extends

* [`ConsentSnapshot`](/kit/api/core#consentsnapshot)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="acceptall" /> `acceptAll` | () => `void` | - | - |
| <a id="analytics" /> `analytics` | `boolean` | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`analytics`](/kit/api/core#analytics-3) |
| <a id="closesettings" /> `closeSettings` | () => `void` | - | - |
| <a id="decidedat" /> `decidedAt` | `string` \| `null` | ISO 8601, when the shopper decided. Null while undecided. | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`decidedAt`](/kit/api/core#decidedat) |
| <a id="draft" /> `draft` | [`ConsentChoice`](/kit/api/core#consentchoice) | The choices in the preferences panel, before they are saved. | - |
| <a id="labels-1" /> `labels` | [`ConsentLabels`](/kit/api/core#consentlabels) | - | - |
| <a id="marketing" /> `marketing` | `boolean` | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`marketing`](/kit/api/core#marketing-2) |
| <a id="open" /> `open` | `boolean` | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`open`](/kit/api/core#open) |
| <a id="opensettings" /> `openSettings` | () => `void` | - | - |
| <a id="rejectall" /> `rejectAll` | () => `void` | - | - |
| <a id="reset" /> `reset` | () => `void` | - | - |
| <a id="revision-1" /> `revision` | `number` | The policy revision the decision was made against. Bump the configured revision when the cookie policy changes materially and every shopper is asked again; a stored decision for an older revision reads as undecided. | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`revision`](/kit/api/core#revision-4) |
| <a id="save" /> `save` | () => `void` | Save the draft. | - |
| <a id="setdraft" /> `setDraft` | (`partial`) => `void` | - | - |
| <a id="status-1" /> `status` | [`ConsentStatus`](/kit/api/core#consentstatus) | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`status`](/kit/api/core#status-2) |
| <a id="visible" /> `visible` | `boolean` | Whether the banner should be on screen: undecided, settings open, or `showWhenDecided`. | - |

***

### ConsentContextValue

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="banners" /> `banners?` | `BannerRegistry` | The explicit `<ConsentBanner>`s mounted below, counted, so the default banner `<ShopkitProvider>` renders can step aside for one the page renders itself. Absent on a context value built by hand. |
| <a id="knownonserver" /> `knownOnServer` | `boolean` | True when the provider was handed `initialState`: the server already knew the decision, so the first paint can show the right banner state instead of waiting for hydration. |
| <a id="lang-2" /> `lang?` | `string` \| `null` | The language the provider was given, the default for every banner and settings button below that is not given one of its own. |
| <a id="serversnapshot" /> `serverSnapshot` | [`ConsentSnapshot`](/kit/api/core#consentsnapshot) | The snapshot a server render — and the hydration pass that has to match it — reads. One stable object per provider: `useSyncExternalStore` compares server snapshots by identity, so a fresh object on every call would loop. |
| <a id="store" /> `store` | [`ConsentStore`](/kit/api/core#consentstore) | - |

***

### ConsentGateProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="category" /> `category` | [`ConsentCategory`](/kit/api/core#consentcategory) | The category the content needs. |
| <a id="children-2" /> `children` | `ReactNode` | - |
| <a id="fallback" /> `fallback?` | `ReactNode` | Rendered while the category is not allowed. Defaults to nothing. |

***

### ConsentProviderProps

#### Extends

* [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="children-3" /> `children` | `ReactNode` | - | - |
| <a id="cookiename" /> `cookieName?` | `string` | Defaults to `qb_consent`. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`cookieName`](/kit/api/core#cookiename-3) |
| <a id="domain" /> `domain?` | `string` | Share the decision across subdomains (`.myshop.com`). Host-only by default. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`domain`](/kit/api/core#domain-1) |
| <a id="initialstate" /> `initialState?` | [`ConsentState`](/kit/api/core#consentstate-1) \| `null` | The state to start from, instead of reading the cookie. For a server render: `readConsentCookie(cookieHeader)` on the server, passed down, so the first paint already knows the decision. Also what a test uses. `null` means "start undecided, do not read the cookie". | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`initialState`](/kit/api/core#initialstate) |
| <a id="lang-3" /> `lang?` | `string` \| `null` | The language of the built-in copy for every `<ConsentBanner>` and `<ConsentSettingsButton>` below that does not set its own. Set it when rendering on the server, so both sides pick the same copy. | - |
| <a id="maxagedays" /> `maxAgeDays?` | `number` | Defaults to 180 days. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`maxAgeDays`](/kit/api/core#maxagedays-1) |
| <a id="persist" /> `persist?` | `boolean` | Write the decision to the cookie. Default true. Set false when another system owns persistence — a third-party consent platform whose callback calls `save()` on this store, or a test. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`persist`](/kit/api/core#persist) |
| <a id="revision-2" /> `revision?` | `number` | The revision a stored decision must match to count. Defaults to 1. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`revision`](/kit/api/core#revision-6) |
| <a id="samesite" /> `sameSite?` | `"lax"` \| `"strict"` \| `"none"` | Defaults to `lax`. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`sameSite`](/kit/api/core#samesite-1) |
| <a id="secure" /> `secure?` | `boolean` | Defaults to true on https origins, false otherwise (localhost works). | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`secure`](/kit/api/core#secure-1) |
| <a id="store-1" /> `store?` | [`ConsentStore`](/kit/api/core#consentstore) | A store you built yourself — one shared with a non-React part of the page, or a bridge to a third-party consent platform (`new ConsentStore({ persist: false })` driven from its callback). Mutually exclusive with the store options; when given, they are ignored. | - |

***

### ConsentSettingsButtonProps

#### Extends

* `Omit`\<`ButtonHTMLAttributes`\<`HTMLButtonElement`>, `"type"` | `"lang"`>

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="lang-4" /> `lang?` | `string` \| `null` | Language of the default label ("Cookie settings", "Cookie-inställningar", …). Defaults to the provider's `lang`, then `<html lang>`. |

***

### JsonLdProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="data-1" /> `data` | \| `Record`\<`string`, `unknown`> \| `Record`\<`string`, `unknown`>\[] \| `null` \| `undefined` | One schema.org node, or several. Empty/absent renders nothing. |

***

### ProductAddToCartState

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="addtocart" /> `addToCart` | (`quantity?`) => `Promise`\<[`Cart`](/kit/api/core#cart-1) \| `null`> | Add the selected variant. No-op (resolving to null) when nothing is selected. |
| <a id="buying" /> `buying` | `boolean` | True while a `buyNow` is adding and starting the checkout. |
| <a id="buynow-2" /> `buyNow` | (`input`, `quantity?`) => `Promise`\<`void`> | Add the selected variant and go straight to the hosted checkout — "Köp nu". The same guard as [addToCart](#addtocart): a no-op while the selection is incomplete, so a product with options can never be bought as a variant the shopper did not choose. Navigates on success; see `useCheckout().buyNow` for the rest of the contract. |
| <a id="buynowerror" /> `buyNowError` | `Error` \| `null` | Why the last `buyNow` failed, or null. Separate from the cart's `error`. |
| <a id="canaddtocart" /> `canAddToCart` | `boolean` | False while the selection is incomplete, a mutation is in flight or a `buyNow` is under way. Gates both buttons — "Lägg i varukorgen" and "Köp nu" add the same thing. |
| <a id="error-2" /> `error` | `Error` \| `null` | - |
| <a id="missingoptions" /> `missingOptions` | [`VariantOptionGroupState`](/kit/api/core#variantoptiongroupstate)\[] | Which option groups still need a choice — for a "Select a size" prompt. |
| <a id="pending-1" /> `pending` | `number` | Number of cart mutations in flight, from the shared cart store. |

***

### ProductImageOptions

Which image to show, and at what size. Shared by `<ProductImage />` and
[useProductImage](#useproductimage).

#### Extended by

* [`ProductImageProps`](#productimageprops)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="alt" /> `alt?` | `string` | Alt text. Defaults to the image's own `altText`, then the product name, then `""` — an empty alt, which is the correct value for an image that merely repeats an adjacent product title. |
| <a id="cachebust" /> `cacheBust?` | `boolean` | Skip the `contentHash` cache-buster. See `ProductImageUrlOptions`. |
| <a id="densities" /> `densities?` | readonly `number`\[] | Pixel ratios for the `srcSet`. Defaults to `[1, 2]` when `width` is set. Pass `[]` for no `srcSet` at all. |
| <a id="height" /> `height?` | `number` | Rendered height in CSS pixels. Set both to reserve layout space. |
| <a id="image" /> `image?` | [`ProductImage`](/kit/api/core#productimage) \| `null` | A specific image record. Wins over every other way of choosing one. |
| <a id="imageid" /> `imageId?` | `number` | Pick by image id rather than by position. |
| <a id="includepending" /> `includePending?` | `boolean` | Include images whose file is still being processed. Off by default. |
| <a id="index" /> `index?` | `number` | Which image, in display order. Defaults to 0 — the main image. |
| <a id="product" /> `product?` | [`Product`](/kit/api/core#product-1) \| `null` | A product you already have — no context, no lookup. Wins over `productId`. |
| <a id="productid" /> `productId?` | `string` \| `number` | The product to take the image from — a prefixed id (`"prod_27"`) or a raw number. Omit it inside a `<ProductProvider>` and the provider's product is used. Outside one it needs a `<ShopkitProvider>` for the client and React 19's `use()`, because the product is then fetched and suspended on (put a `<Suspense>` above it). |
| <a id="slug" /> `slug?` | `string` | Look the product up by slug instead of by id. See the note on `productId`. |
| <a id="transform" /> `transform?` | [`ImageTransform`](/kit/api/core#imagetransform) | Quality, format, fit, crop — see [ImageTransform](/kit/api/core#imagetransform). |
| <a id="width" /> `width?` | `number` | Rendered width in CSS pixels. Drives the CDN resize, the `srcSet` and the `<img width>` that reserves the space before the bytes land. |
| <a id="widths" /> `widths?` | readonly `number`\[] | Candidate widths for the `srcSet`, as `w` descriptors — for an image that reflows with the viewport. Pair it with `sizes`. Wins over `densities`. |

***

### ProductImageProps

Which image to show, and at what size. Shared by `<ProductImage />` and
[useProductImage](#useproductimage).

#### Extends

* [`ProductImageOptions`](#productimageoptions).`Omit`\<`ComponentPropsWithoutRef`\<`"img"`>, `"src"` | `"srcSet"` | `"width"` | `"height"` | `"alt"`>

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="alt-1" /> `alt?` | `string` | Alt text. Defaults to the image's own `altText`, then the product name, then `""` — an empty alt, which is the correct value for an image that merely repeats an adjacent product title. | [`ProductImageOptions`](#productimageoptions).[`alt`](#alt) |
| <a id="cachebust-1" /> `cacheBust?` | `boolean` | Skip the `contentHash` cache-buster. See `ProductImageUrlOptions`. | [`ProductImageOptions`](#productimageoptions).[`cacheBust`](#cachebust) |
| <a id="densities-1" /> `densities?` | readonly `number`\[] | Pixel ratios for the `srcSet`. Defaults to `[1, 2]` when `width` is set. Pass `[]` for no `srcSet` at all. | [`ProductImageOptions`](#productimageoptions).[`densities`](#densities) |
| <a id="fallback-1" /> `fallback?` | `ReactNode` | Rendered when the product has no image — a placeholder box, or nothing. Defaults to nothing, so a missing image never becomes a broken one. | - |
| <a id="height-1" /> `height?` | `number` | Rendered height in CSS pixels. Set both to reserve layout space. | [`ProductImageOptions`](#productimageoptions).[`height`](#height) |
| <a id="image-1" /> `image?` | [`ProductImage`](/kit/api/core#productimage) \| `null` | A specific image record. Wins over every other way of choosing one. | [`ProductImageOptions`](#productimageoptions).[`image`](#image) |
| <a id="imageid-1" /> `imageId?` | `number` | Pick by image id rather than by position. | [`ProductImageOptions`](#productimageoptions).[`imageId`](#imageid) |
| <a id="includepending-1" /> `includePending?` | `boolean` | Include images whose file is still being processed. Off by default. | [`ProductImageOptions`](#productimageoptions).[`includePending`](#includepending) |
| <a id="index-1" /> `index?` | `number` | Which image, in display order. Defaults to 0 — the main image. | [`ProductImageOptions`](#productimageoptions).[`index`](#index) |
| <a id="priority" /> `priority?` | `boolean` | Above the fold: load eagerly at high priority instead of lazily. Use it for the one image that is the page's largest contentful paint, never for a grid. | - |
| <a id="product-1" /> `product?` | [`Product`](/kit/api/core#product-1) \| `null` | A product you already have — no context, no lookup. Wins over `productId`. | [`ProductImageOptions`](#productimageoptions).[`product`](#product) |
| <a id="productid-1" /> `productId?` | `string` \| `number` | The product to take the image from — a prefixed id (`"prod_27"`) or a raw number. Omit it inside a `<ProductProvider>` and the provider's product is used. Outside one it needs a `<ShopkitProvider>` for the client and React 19's `use()`, because the product is then fetched and suspended on (put a `<Suspense>` above it). | [`ProductImageOptions`](#productimageoptions).[`productId`](#productid) |
| <a id="slug-1" /> `slug?` | `string` | Look the product up by slug instead of by id. See the note on `productId`. | [`ProductImageOptions`](#productimageoptions).[`slug`](#slug) |
| <a id="transform-1" /> `transform?` | [`ImageTransform`](/kit/api/core#imagetransform) | Quality, format, fit, crop — see [ImageTransform](/kit/api/core#imagetransform). | [`ProductImageOptions`](#productimageoptions).[`transform`](#transform) |
| <a id="width-1" /> `width?` | `number` | Rendered width in CSS pixels. Drives the CDN resize, the `srcSet` and the `<img width>` that reserves the space before the bytes land. | [`ProductImageOptions`](#productimageoptions).[`width`](#width) |
| <a id="widths-1" /> `widths?` | readonly `number`\[] | Candidate widths for the `srcSet`, as `w` descriptors — for an image that reflows with the viewport. Pair it with `sizes`. Wins over `densities`. | [`ProductImageOptions`](#productimageoptions).[`widths`](#widths) |

***

### ProductImageState

What [useProductImage](#useproductimage) resolved.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="alt-2" /> `alt` | `string` | - |
| <a id="height-2" /> `height?` | `number` | - |
| <a id="image-2" /> `image` | [`ProductImage`](/kit/api/core#productimage) \| `null` | The chosen image record, or null when the product has no usable image. |
| <a id="product-2" /> `product` | [`Product`](/kit/api/core#product-1) \| `null` | The product the image came from, when one was resolvable. |
| <a id="src" /> `src` | `string` \| `null` | Absolute, sized `src`. Null when there is no image to show. |
| <a id="srcset" /> `srcSet` | `string` \| `null` | Matching `srcSet`, or null when one would add nothing. |
| <a id="width-2" /> `width?` | `number` | - |

***

### ProductOptionGroupProps

#### Extends

* `RenderProp`\<[`VariantOptionGroupState`](/kit/api/core#variantoptiongroupstate)>

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="children-4" /> `children` | (`state`) => `ReactNode` | - | `RenderProp.children` |
| <a id="fallback-2" /> `fallback?` | `ReactNode` | Rendered when the product has no such group. Defaults to nothing. | - |
| <a id="option" /> `option` | `string` \| `number` | Option id, or its name ("Color", case-insensitive). | - |

***

### ProductOptionValuesProps

#### Extends

* `RenderProp`\<[`VariantOptionValueState`](/kit/api/core#variantoptionvaluestate)>

#### Properties

| Property | Type | Inherited from |
| - | - | - |
| <a id="children-5" /> `children` | (`state`) => `ReactNode` | `RenderProp.children` |
| <a id="fallback-3" /> `fallback?` | `ReactNode` | - |
| <a id="option-1" /> `option` | `string` \| `number` | - |

***

### ProductProviderProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="children-6" /> `children` | `ReactNode` | - |
| <a id="currency-1" /> `currency?` | `string` | Fallback ISO 4217 code for display, used only when the product does not state its own (`product.currency`, from a platform older than the field). |
| <a id="id" /> `id?` | `string` \| `number` | Look the product up by id — `"prod_27"` or `27`. One request. |
| <a id="initialvariantid" /> `initialVariantId?` | `number` | Preselect this variant — a deep link, or a "recently viewed" restore. |
| <a id="notfound" /> `notFound?` | `ReactNode` | Rendered instead of `children` when the lookup finds nothing. Defaults to nothing at all. A Next route usually wants `notFound()` at the page level rather than a fallback here, so that the response is a real 404. |
| <a id="onvariantchange" /> `onVariantChange?` | (`variant`) => `void` | Called whenever the resolved variant changes, including to null. |
| <a id="product-3" /> `product?` | \| [`Product`](/kit/api/core#product-1) \| `Promise`\<[`Product`](/kit/api/core#product-1) \| `null`> \| `null` | The product, already resolved — or a promise for it. A promise is the React 19 server→client idiom: a server component starts the fetch WITHOUT awaiting, passes the promise down, and this unwraps it with `use()` so the nearest `<Suspense>` shows the fallback. The page shell streams immediately instead of blocking on the product. Wins over `slug`/`id` when both are given. |
| <a id="selectfirstavailable" /> `selectFirstAvailable?` | `boolean` | Preselect the first purchasable variant instead of starting empty. Off by default: an empty start shows a price RANGE, which is honest for a product whose variants differ in price, and it does not put words in the shopper's mouth about which colour they wanted. |
| <a id="slug-2" /> `slug?` | `string` | Look the product up by URL slug. Needs a `<ShopkitProvider>` for the client, and React 19 for `use()`. The lookup promise is cached per (client, slug), which is what makes suspending on it terminate — see `state/product-cache.ts`. Note the cost: `products.getBySlug` walks the product list because the storefront API has no slug filter. Fine for a page render; on a large catalog prefer resolving the product on the server and passing it (or its promise) instead. |

***

### ProductState

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="clear-2" /> `clear` | (`optionId`) => `void` | Unset one group's choice. |
| <a id="currency-2" /> `currency?` | `string` | The currency the product's prices are in — `product.currency`, else the `currency` passed to the provider — for the app's own formatting. |
| <a id="hasoptions" /> `hasOptions` | `boolean` | False for a simple product — render no picker. |
| <a id="iscomplete" /> `isComplete` | `boolean` | True once every option group has a choice (always true with no options). |
| <a id="options" /> `options` | [`VariantOptionGroupState`](/kit/api/core#variantoptiongroupstate)\[] | Option groups with per-value `selected` / `available` flags, in display order. |
| <a id="price" /> `price` | [`VariantPriceState`](/kit/api/core#variantpricestate) | Price for the current selection: exact figure, or a range before that. |
| <a id="product-4" /> `product` | [`Product`](/kit/api/core#product-1) | - |
| <a id="reset-1" /> `reset` | () => `void` | Unset every choice. |
| <a id="select" /> `select` | (`optionId`, `valueId`) => `void` | Choose a value. Contradicting choices are cleared, not refused. |
| <a id="selectedvariant" /> `selectedVariant` | [`ProductVariant`](/kit/api/core#productvariant) \| `null` | The variant the selection identifies, or null while it is incomplete. |
| <a id="selection" /> `selection` | [`VariantSelection`](/kit/api/core#variantselection) | The raw selection, keyed by option id. Partial until every group is chosen. |
| <a id="selectvariant" /> `selectVariant` | (`variantId`) => `void` | Jump straight to a variant, e.g. from a `?variant=` deep link. |

***

### SeoProps

What the page is about. Exactly one of these shapes is used.

#### Extends

* [`SeoInput`](/kit/api/core#seoinput).[`SeoDefaults`](/kit/api/core#seodefaults)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="baseurl" /> `baseUrl?` | `string` | Absolute origin of the storefront, e.g. `https://myshop.com`. Required for canonical URLs and for turning a relative image path into an absolute one — crawlers reject relative values in both places. | [`SeoDefaults`](/kit/api/core#seodefaults).[`baseUrl`](/kit/api/core#baseurl-2) |
| <a id="breadcrumbs" /> `breadcrumbs?` | [`SeoBreadcrumb`](/kit/api/core#seobreadcrumb)\[] | Trail for BreadcrumbList when the page builds its own. | [`SeoInput`](/kit/api/core#seoinput).[`breadcrumbs`](/kit/api/core#breadcrumbs) |
| <a id="category-1" /> `category?` | [`Category`](/kit/api/core#category) \| `null` | A category page — emits BreadcrumbList structured data. | [`SeoInput`](/kit/api/core#seoinput).[`category`](/kit/api/core#category-1) |
| <a id="categorypath" /> `categoryPath?` | `string` | The same for a category — `"/categories/{slug}"`. | [`SeoDefaults`](/kit/api/core#seodefaults).[`categoryPath`](/kit/api/core#categorypath-1) |
| <a id="currency-3" /> `currency?` | `string` | ISO 4217 code for prices in structured data. A product's own `product.currency` wins; this covers a product from a platform older than that field. With neither, no price is emitted. | [`SeoDefaults`](/kit/api/core#seodefaults).[`currency`](/kit/api/core#currency-18) |
| <a id="defaultimage" /> `defaultImage?` | `string` \| [`SeoImage`](/kit/api/core#seoimage) | Fallback image for pages with none of their own. | [`SeoDefaults`](/kit/api/core#seodefaults).[`defaultImage`](/kit/api/core#defaultimage) |
| <a id="description" /> `description?` | `string` | - | [`SeoInput`](/kit/api/core#seoinput).[`description`](/kit/api/core#description-4) |
| <a id="images" /> `images?` | (`string` \| [`SeoImage`](/kit/api/core#seoimage))\[] | - | [`SeoInput`](/kit/api/core#seoinput).[`images`](/kit/api/core#images-2) |
| <a id="jsonld" /> `jsonLd?` | `Record`\<`string`, `unknown`>\[] | Extra structured data to emit as-is. | [`SeoInput`](/kit/api/core#seoinput).[`jsonLd`](/kit/api/core#jsonld) |
| <a id="locale" /> `locale?` | `string` | BCP 47 locale for OpenGraph (`sv_SE`). Derived from the shop when absent. | [`SeoDefaults`](/kit/api/core#seodefaults).[`locale`](/kit/api/core#locale-1) |
| <a id="meta" /> `meta?` | `object`\[] | - | [`SeoInput`](/kit/api/core#seoinput).[`meta`](/kit/api/core#meta) |
| <a id="nofollow" /> `noFollow?` | `boolean` | - | [`SeoInput`](/kit/api/core#seoinput).[`noFollow`](/kit/api/core#nofollow) |
| <a id="noindex" /> `noIndex?` | `boolean` | Keep the page out of the index — a search results or filter page. | [`SeoInput`](/kit/api/core#seoinput).[`noIndex`](/kit/api/core#noindex) |
| <a id="organization" /> `organization?` | `boolean` | Emit `Organization` structured data alongside the page's own. | [`SeoDefaults`](/kit/api/core#seodefaults).[`organization`](/kit/api/core#organization) |
| <a id="product-5" /> `product?` | [`Product`](/kit/api/core#product-1) \| `null` | A product page — emits Product structured data with offers. | [`SeoInput`](/kit/api/core#seoinput).[`product`](/kit/api/core#product-3) |
| <a id="productpath" /> `productPath?` | `string` | Where a product lives, as a template — `"/products/{slug}"`. Lets `<SEO />` build its own canonical from the product in context, so a page inside `<ProductProvider>` needs no props at all. `{slug}` and `{id}` are substituted; a product with no slug falls back to `{id}`. A template rather than a function because these defaults cross a client boundary and get compared by value — a function would neither serialize nor memoize. | [`SeoDefaults`](/kit/api/core#seodefaults).[`productPath`](/kit/api/core#productpath) |
| <a id="shop" /> `shop?` | [`Shop`](/kit/api/core#shop-2) \| `null` | The shop, for site name, logo, locale and Organization structured data. | [`SeoDefaults`](/kit/api/core#seodefaults).[`shop`](/kit/api/core#shop-1) |
| <a id="sitename" /> `siteName?` | `string` | Shown after the page title. Defaults to the shop's name when a shop is given. | [`SeoDefaults`](/kit/api/core#seodefaults).[`siteName`](/kit/api/core#sitename) |
| <a id="skipjsonld" /> `skipJsonLd?` | `boolean` | Skip the `<script type="application/ld+json">` blocks. | - |
| <a id="skiptitle" /> `skipTitle?` | `boolean` | Skip `<title>`. Set this when the framework owns the title (a Next `metadata` export, or `react-helmet`) to avoid two competing titles. | - |
| <a id="title" /> `title?` | `string` | Overrides whatever the product/category/shop would have produced. | [`SeoInput`](/kit/api/core#seoinput).[`title`](/kit/api/core#title-7) |
| <a id="titletemplate" /> `titleTemplate?` | `string` | How to compose the final title. `%s` is the page title. Defaults to `"%s · {siteName}"`, and to the bare title with no site name. | [`SeoDefaults`](/kit/api/core#seodefaults).[`titleTemplate`](/kit/api/core#titletemplate) |
| <a id="twittercreator" /> `twitterCreator?` | `string` | - | [`SeoDefaults`](/kit/api/core#seodefaults).[`twitterCreator`](/kit/api/core#twittercreator) |
| <a id="twittersite" /> `twitterSite?` | `string` | `@handle` of the site, for Twitter cards. | [`SeoDefaults`](/kit/api/core#seodefaults).[`twitterSite`](/kit/api/core#twittersite) |
| <a id="url" /> `url?` | `string` | Absolute URL, or a path resolved against `baseUrl`. | [`SeoInput`](/kit/api/core#seoinput).[`url`](/kit/api/core#url-6) |

***

### SeoProviderProps

Site-wide values every page shares. Supply once (via `SeoProvider` or as an
argument) so a page only has to name what is specific to it.

#### Extends

* [`SeoDefaults`](/kit/api/core#seodefaults)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="baseurl-1" /> `baseUrl?` | `string` | Absolute origin of the storefront, e.g. `https://myshop.com`. Required for canonical URLs and for turning a relative image path into an absolute one — crawlers reject relative values in both places. | [`SeoDefaults`](/kit/api/core#seodefaults).[`baseUrl`](/kit/api/core#baseurl-2) |
| <a id="categorypath-1" /> `categoryPath?` | `string` | The same for a category — `"/categories/{slug}"`. | [`SeoDefaults`](/kit/api/core#seodefaults).[`categoryPath`](/kit/api/core#categorypath-1) |
| <a id="children-7" /> `children` | `ReactNode` | - | - |
| <a id="currency-4" /> `currency?` | `string` | ISO 4217 code for prices in structured data. A product's own `product.currency` wins; this covers a product from a platform older than that field. With neither, no price is emitted. | [`SeoDefaults`](/kit/api/core#seodefaults).[`currency`](/kit/api/core#currency-18) |
| <a id="defaultimage-1" /> `defaultImage?` | `string` \| [`SeoImage`](/kit/api/core#seoimage) | Fallback image for pages with none of their own. | [`SeoDefaults`](/kit/api/core#seodefaults).[`defaultImage`](/kit/api/core#defaultimage) |
| <a id="locale-1" /> `locale?` | `string` | BCP 47 locale for OpenGraph (`sv_SE`). Derived from the shop when absent. | [`SeoDefaults`](/kit/api/core#seodefaults).[`locale`](/kit/api/core#locale-1) |
| <a id="organization-1" /> `organization?` | `boolean` | Emit `Organization` structured data alongside the page's own. | [`SeoDefaults`](/kit/api/core#seodefaults).[`organization`](/kit/api/core#organization) |
| <a id="productpath-1" /> `productPath?` | `string` | Where a product lives, as a template — `"/products/{slug}"`. Lets `<SEO />` build its own canonical from the product in context, so a page inside `<ProductProvider>` needs no props at all. `{slug}` and `{id}` are substituted; a product with no slug falls back to `{id}`. A template rather than a function because these defaults cross a client boundary and get compared by value — a function would neither serialize nor memoize. | [`SeoDefaults`](/kit/api/core#seodefaults).[`productPath`](/kit/api/core#productpath) |
| <a id="shop-1" /> `shop?` | [`Shop`](/kit/api/core#shop-2) \| `null` | The shop, for site name, logo, locale and Organization structured data. | [`SeoDefaults`](/kit/api/core#seodefaults).[`shop`](/kit/api/core#shop-1) |
| <a id="sitename-1" /> `siteName?` | `string` | Shown after the page title. Defaults to the shop's name when a shop is given. | [`SeoDefaults`](/kit/api/core#seodefaults).[`siteName`](/kit/api/core#sitename) |
| <a id="titletemplate-1" /> `titleTemplate?` | `string` | How to compose the final title. `%s` is the page title. Defaults to `"%s · {siteName}"`, and to the bare title with no site name. | [`SeoDefaults`](/kit/api/core#seodefaults).[`titleTemplate`](/kit/api/core#titletemplate) |
| <a id="twittercreator-1" /> `twitterCreator?` | `string` | - | [`SeoDefaults`](/kit/api/core#seodefaults).[`twitterCreator`](/kit/api/core#twittercreator) |
| <a id="twittersite-1" /> `twitterSite?` | `string` | `@handle` of the site, for Twitter cards. | [`SeoDefaults`](/kit/api/core#seodefaults).[`twitterSite`](/kit/api/core#twittersite) |

***

### ShopkitConsentOptions

The `consent` prop of `<ShopkitProvider>` as an object: the consent store's
options, the default banner's, and the analytics switch.

#### Extends

* `Omit`\<[`ConsentStoreOptions`](/kit/api/core#consentstoreoptions), `"initialState"`>

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="analytics-1" /> `analytics?` | `boolean` \| [`ShopkitAnalyticsOptions`](#shopkitanalyticsoptions) | The shop's own GA4 / GTM / Meta pixel, gated on this consent. Default true. `false` keeps the banner and loads no pixel; an object is the `<AnalyticsProvider>` options (extra `destinations`, `auto: false`, …). | - |
| <a id="banner" /> `banner?` | `boolean` \| (`state`) => `ReactNode` | The banner. Default true: the kit's accessible dialog. `false` renders none (render your own with `useConsent()` or `<ConsentBanner>`); a function is the render prop `<ConsentBanner>` takes, rendered in its place. | - |
| <a id="classname-2" /> `className?` | `string` | Extra class on the default banner's root. | - |
| <a id="cookiename-1" /> `cookieName?` | `string` | Defaults to `qb_consent`. | [`ConsentCookieOptions`](/kit/api/core#consentcookieoptions).[`cookieName`](/kit/api/core#cookiename-1) |
| <a id="domain-1" /> `domain?` | `string` | Share the decision across subdomains (`.myshop.com`). Host-only by default. | [`ConsentCookieWriteOptions`](/kit/api/core#consentcookiewriteoptions).[`domain`](/kit/api/core#domain) |
| <a id="initialstate-1" /> `initialState?` | [`ConsentState`](/kit/api/core#consentstate-1) \| `null` | The decision as the server read it (`await shopkit.consent.read()`), so the first paint already shows the right banner state: no banner for a shopper who decided, no flash for one who has not. Without it the banner waits for hydration before it shows anything. | - |
| <a id="labels-2" /> `labels?` | `Partial`\<[`ConsentLabels`](/kit/api/core#consentlabels)> \| `null` | Overrides for any of the banner's built-in strings. | - |
| <a id="lang-5" /> `lang?` | `string` \| `null` | Language of the built-in copy, for the banner and every `<ConsentSettingsButton>` below. Set it when rendering on the server. | - |
| <a id="maxagedays-1" /> `maxAgeDays?` | `number` | Defaults to 180 days. | [`ConsentCookieWriteOptions`](/kit/api/core#consentcookiewriteoptions).[`maxAgeDays`](/kit/api/core#maxagedays) |
| <a id="persist-1" /> `persist?` | `boolean` | Write the decision to the cookie. Default true. Set false when another system owns persistence — a third-party consent platform whose callback calls `save()` on this store, or a test. | [`ConsentStoreOptions`](/kit/api/core#consentstoreoptions).[`persist`](/kit/api/core#persist) |
| <a id="privacypolicyurl-1" /> `privacyPolicyUrl?` | `string` \| `null` | Adds a "Privacy policy" link to the banner. | - |
| <a id="revision-3" /> `revision?` | `number` | The revision a stored decision must match to count. Defaults to 1. | [`ConsentCookieOptions`](/kit/api/core#consentcookieoptions).[`revision`](/kit/api/core#revision-2) |
| <a id="samesite-1" /> `sameSite?` | `"lax"` \| `"strict"` \| `"none"` | Defaults to `lax`. | [`ConsentCookieWriteOptions`](/kit/api/core#consentcookiewriteoptions).[`sameSite`](/kit/api/core#samesite) |
| <a id="secure-1" /> `secure?` | `boolean` | Defaults to true on https origins, false otherwise (localhost works). | [`ConsentCookieWriteOptions`](/kit/api/core#consentcookiewriteoptions).[`secure`](/kit/api/core#secure) |
| <a id="store-2" /> `store?` | [`ConsentStore`](/kit/api/core#consentstore) | A consent store you built yourself (one shared with a non-React part of the page, or a bridge to a third-party consent platform). The store options are then ignored. | - |
| <a id="unstyled-1" /> `unstyled?` | `boolean` | Leave out the default banner's stylesheet. | - |

***

### ShopkitContextValue

#### Properties

| Property | Type |
| - | - |
| <a id="cartstore-1" /> `cartStore` | [`CartStore`](#cartstore) |
| <a id="client" /> `client` | [`ShopkitClient`](/kit/api/core#shopkitclient) |

***

### ShopkitProviderProps

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="children-8" /> `children` | `ReactNode` | - |
| <a id="client-1" /> `client?` | [`ShopkitClient`](/kit/api/core#shopkitclient) | A client you built yourself. Mutually exclusive with `config`. |
| <a id="config" /> `config?` | [`ShopkitConfig`](/kit/api/core#shopkitconfig) | Config for a client the provider builds and owns. |
| <a id="consent" /> ~~`consent?`~~ | `false` \| [`ShopkitConsentOptions`](#shopkitconsentoptions) | Options for the cookie consent and analytics the provider mounts. They are **on by default**: the provider shows a consent banner, keeps the decision in a `qb_consent` cookie, loads the shop's own GA4 / GTM / Meta pixel (from its settings in the Quickbutik admin) once the shopper agrees, and forwards the decision to the hosted checkout. Nothing from Google or Meta is loaded before that, and Meta only on `marketing`. This prop shapes it (`initialState`, `lang`, `privacyPolicyUrl`, `analytics`, `banner`, …); it does not switch it off. The one switch is `consent: false` in the client config, which the server client's checkout handoff reads too, so browser and server agree. **Deprecated** `consent={false}` still turns consent off for this tree, but a server-started checkout does not see the prop and keeps forwarding the decision; it logs a warning. Set `consent: false` in the config instead. Inside a tree that already has consent (a `<ConsentProvider>` of your own, or an outer `<ShopkitProvider>` — a campaign provider inside the site's), that store is reused and no second banner is rendered; an analytics hub above is reused the same way, with this provider's cart reporting into it. |
| <a id="currency-5" /> `currency?` | `string` \| `null` | The currency to browse in — shorthand for `config.currency`, so it is the DEFAULT: a currency the shopper picked with `setCurrency()` / `useCurrency()` is remembered and wins on a later visit. A later CHANGE of the prop switches the client (`setCurrency`) without rebuilding it: catalog hooks refetch and the SAME cart is re-read in the new currency. A change to `null` is `setCurrency(null)`: it forgets the choice and returns to the DEFAULT, i.e. the value the provider mounted with, not necessarily the shop's own currency. Server-render it from the same remembered value (the `qb_currency` cookie, see the storage guide) and the server and the first client paint agree. With a prebuilt `client` there is no config to merge into, so the value at mount is ignored (give that client its `currency` when you build it) and only a later change is forwarded. |
| <a id="initialcart" /> `initialCart?` | [`Cart`](/kit/api/core#cart-1) \| `null` | A cart already fetched on the server, so the first paint shows the real basket instead of an empty one that fills in a moment later. |
| <a id="storefrontid" /> `storefrontId?` | `string` \| `null` | Sell into this campaign storefront (`sf_…`) — shorthand for `config.storefrontId`, merged into the config the provider builds. Every product read, cart and checkout below is priced for and attributed to the campaign. Changing it rebuilds the client and the cart store: a campaign's cart is priced differently and remembered under its own cookie, so the subtree must not keep showing the previous campaign's basket. "Changing" means a different campaign: ids compare case-insensitively, and the same id moving between the prop and `config.storefrontId` is no change. With a prebuilt `client` the prop can only CONFIRM that client's binding: a client's campaign is fixed when it is built, so a `client` bound to a different campaign, or to none, throws — build it with `createShopkitClient({ storefrontId })` instead. `null` / `""` mean "not set", for `storefrontId={campaign?.id ?? null}`. |

***

### UseCartResult

#### Extends

* [`CartSnapshot`](#cartsnapshot)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="add-2" /> `add` | (`item`) => `Promise`\<[`Cart`](/kit/api/core#cart-1) \| `null`> | Add a product; creates the cart on first use. | - |
| <a id="cart-1" /> `cart` | [`Cart`](/kit/api/core#cart-1) \| `null` | - | [`CartSnapshot`](#cartsnapshot).[`cart`](#cart) |
| <a id="clear-3" /> `clear` | () => `Promise`\<`void`> | Empty the basket: delete the remembered cart server-side and forget it, so the next `add` starts a fresh one. A cart the server already dropped still counts as cleared; with no cart at all it does nothing. See `cart.clear()`. | - |
| <a id="clearcart-1" /> `clearCart` | () => `Promise`\<`void`> | Alias of [clear](#clear-3) — the same function, under the name people search for. | - |
| <a id="error-3" /> `error` | `Error` \| `null` | - | [`CartSnapshot`](#cartsnapshot).[`error`](#error-1) |
| <a id="itemcount-1" /> `itemCount` | `number` | Sum of item quantities; 0 when there is no cart. Convenience for badges. | [`CartSnapshot`](#cartsnapshot).[`itemCount`](#itemcount) |
| <a id="pending-2" /> `pending` | `number` | Number of mutations in flight — drives an "updating…" affordance. | [`CartSnapshot`](#cartsnapshot).[`pending`](#pending) |
| <a id="refresh-2" /> `refresh` | () => `Promise`\<[`Cart`](/kit/api/core#cart-1) \| `null`> | Re-read from the server. | - |
| <a id="removeitem-2" /> `removeItem` | (`itemId`) => `Promise`\<[`Cart`](/kit/api/core#cart-1) \| `null`> | - | - |
| <a id="revision-4" /> `revision` | `number` | Bumped once per SUCCESSFUL mutation made through this store — `add`, `updateItem`, `removeItem`, `clear`, `buyNow` — and by nothing else. Not a change detector: `load`, `refresh` and `hydrate` leave it alone even when they bring back a different cart, and a failed mutation leaves it alone too. It answers exactly one question, "did this page just change the cart?", which is the question an embedded checkout has to be told about (see `EmbeddedCheckout.cartUpdated`) and a re-read from the server is not. Comparing it across snapshots is how a consumer notices; the absolute value means nothing. | [`CartSnapshot`](#cartsnapshot).[`revision`](#revision) |
| <a id="status-2" /> `status` | [`CartStatus`](#cartstatus) | - | [`CartSnapshot`](#cartsnapshot).[`status`](#status) |
| <a id="updateitem-2" /> `updateItem` | (`itemId`, `quantity`) => `Promise`\<[`Cart`](/kit/api/core#cart-1) \| `null`> | Set an absolute quantity on a cart LINE id. 0 removes the line. | - |

***

### UseCheckoutResult

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="buynow-3" /> `buyNow` | (`item`, `input`) => `Promise`\<`void`> | Add one product to the remembered cart and go straight to the hosted checkout — the "Köp nu" button. Navigates like [redirectToCheckout](#redirecttocheckout); an existing basket is carried along, not replaced. Shares `starting`, `error` and the double-click guard with the other two, and keeps the shared cart store in step (see `CartStore.buyNow`), so every badge on the page sees the add. Inside a `<ProductProvider>`, `useProductAddToCart().buyNow` is the variant-aware version of this. |
| <a id="error-4" /> `error` | `Error` \| `null` | - |
| <a id="redirecttocheckout" /> `redirectToCheckout` | (`input`) => `Promise`\<`void`> | Create the session and navigate the browser to the hosted checkout. |
| <a id="start" /> `start` | (`input`) => `Promise`\<[`StartCheckoutResult`](/kit/api/core#startcheckoutresult) \| `null`> | Create the session and return the hosted checkout URL. Does NOT navigate — see [redirectToCheckout](#redirecttocheckout) for that. |
| <a id="starting" /> `starting` | `boolean` | True while the session is being created — and, after `redirectToCheckout` or `buyNow` has navigated, until the page is restored from the back/forward cache, so a click while the checkout loads does nothing. |
| <a id="url-1" /> `url` | `string` \| `null` | The URL from the last successful `start`. |

***

### UseConsentResult

What the store exposes: the decision plus the one piece of UI state a banner
and a far-away "Cookie settings" link have to share — whether the
preferences panel is open. Not persisted.

#### Extends

* [`ConsentSnapshot`](/kit/api/core#consentsnapshot)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="acceptall-1" /> `acceptAll` | () => `void` | - | - |
| <a id="allows" /> `allows` | (`category`) => `boolean` | Whether a category may be used right now. `necessary` always may; undecided is a no. | - |
| <a id="analytics-2" /> `analytics` | `boolean` | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`analytics`](/kit/api/core#analytics-3) |
| <a id="closesettings-1" /> `closeSettings` | () => `void` | - | - |
| <a id="decidedat-1" /> `decidedAt` | `string` \| `null` | ISO 8601, when the shopper decided. Null while undecided. | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`decidedAt`](/kit/api/core#decidedat) |
| <a id="marketing-1" /> `marketing` | `boolean` | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`marketing`](/kit/api/core#marketing-2) |
| <a id="open-1" /> `open` | `boolean` | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`open`](/kit/api/core#open) |
| <a id="opensettings-1" /> `openSettings` | () => `void` | Open the preferences panel — from a footer "Cookie settings" link, say. | - |
| <a id="rejectall-1" /> `rejectAll` | () => `void` | Necessary only. | - |
| <a id="reset-2" /> `reset` | () => `void` | Withdraw: forget the decision, ask again. | - |
| <a id="revision-5" /> `revision` | `number` | The policy revision the decision was made against. Bump the configured revision when the cookie policy changes materially and every shopper is asked again; a stored decision for an older revision reads as undecided. | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`revision`](/kit/api/core#revision-4) |
| <a id="save-1" /> `save` | (`choice`) => `void` | - | - |
| <a id="status-3" /> `status` | [`ConsentStatus`](/kit/api/core#consentstatus) | - | [`ConsentSnapshot`](/kit/api/core#consentsnapshot).[`status`](/kit/api/core#status-2) |
| <a id="store-3" /> `store` | [`ConsentStore`](/kit/api/core#consentstore) | - | - |

***

### UseCurrencyResult

What a currency choice means for one shop. See [describeCurrency](/kit/api/core#describecurrency).

#### Extends

* [`CurrencyInfo`](/kit/api/core#currencyinfo)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="basecurrency" /> `baseCurrency` | `string` \| `null` | The shop's own (base) currency, or null when the shop is not known yet. | [`CurrencyInfo`](/kit/api/core#currencyinfo).[`baseCurrency`](/kit/api/core#basecurrency-2) |
| <a id="chargecurrency" /> `chargeCurrency` | `string` \| `null` | What the checkout will charge: the base currency, or `currency` in charge mode. | [`CurrencyInfo`](/kit/api/core#currencyinfo).[`chargeCurrency`](/kit/api/core#chargecurrency-1) |
| <a id="currencies" /> `currencies` | [`ShopCurrency`](/kit/api/core#shopcurrency-1)\[] | Every currency the shop offers, base first. Empty until the shop is known. | [`CurrencyInfo`](/kit/api/core#currencyinfo).[`currencies`](/kit/api/core#currencies) |
| <a id="currency-6" /> `currency` | `string` \| `null` | The currency prices are shown in: the chosen code when the shop offers it, else the shop's own currency. Null only before the shop is known and with nothing chosen. | [`CurrencyInfo`](/kit/api/core#currencyinfo).[`currency`](/kit/api/core#currency-9) |
| <a id="error-5" /> `error` | `Error` \| `null` | - | - |
| <a id="isloading" /> `isLoading` | `boolean` | True while the shop (and with it the list of currencies) is loading. | - |
| <a id="mode" /> `mode` | [`ShopCurrencyMode`](/kit/api/core#shopcurrencymode-1) | - `"base"` — the shop's own currency; nothing is converted. - `"display"` — prices are SHOWN converted, and the checkout charges the shop's currency (with the converted amount as an approximation). - `"charge"` — priced and charged in `currency`. | [`CurrencyInfo`](/kit/api/core#currencyinfo).[`mode`](/kit/api/core#mode-1) |
| <a id="rate" /> `rate` | `number` | Units of `currency` per 1 unit of the base currency. 1 for `"base"`. | [`CurrencyInfo`](/kit/api/core#currencyinfo).[`rate`](/kit/api/core#rate) |
| <a id="selected" /> `selected` | `string` \| `null` | What was asked for — the shopper's remembered choice, else the configured default — whether or not the shop offers it. `currency` is what prices are actually shown in. | - |
| <a id="setcurrency" /> `setCurrency` | (`currency`) => `Promise`\<`void`> | Browse in another currency. Every catalog hook refetches, `useCart()` re-reads the same cart in it, and the choice is remembered. `null` returns to the default. | - |

***

### UseOrderConfirmationOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="enabled" /> `enabled?` | `boolean` | Default true. False never polls (a settled `initialConfirmation` never does either). |
| <a id="initialconfirmation" /> `initialConfirmation?` | [`SessionConfirmation`](/kit/api/core#sessionconfirmation) \| `null` | The snapshot the server already took (`shopkit.checkout.confirmation()`). `completed` or `failed` is final: no polling, and a completed one is reported as the `purchase`. |
| <a id="trackpurchase" /> `trackPurchase?` | `boolean` | Report the `purchase` event once the order exists. Default true. |

***

### UseOrderConfirmationResult

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="confirmation-1" /> `confirmation` | [`SessionConfirmation`](/kit/api/core#sessionconfirmation) \| `null` | The most recent raw snapshot from the server. |
| <a id="error-6" /> `error` | `Error` \| `null` | - |
| <a id="loading-1" /> `loading` | `boolean` | True while still polling. |
| <a id="ordernumber" /> `orderNumber` | `number` \| `null` | - |
| <a id="outcome" /> `outcome` | [`ConfirmationOutcome`](/kit/api/core#confirmationoutcome) \| `null` | Set once polling reaches a terminal state (or gives up). |
| <a id="status-4" /> `status` | [`ConfirmationStatus`](/kit/api/core#confirmationstatus) \| `null` | - |

***

### UseTrackPurchaseInput

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="affiliation" /> `affiliation?` | `string` \| `null` | GA4 `affiliation` — the store the order was placed with. |
| <a id="enabled-1" /> `enabled?` | `boolean` | Default true. |
| <a id="ordernumber-1" /> `orderNumber` | `string` \| `number` \| `null` \| `undefined` | The order number. Nothing happens until it is known. |
| <a id="sessionid" /> `sessionId?` | `string` \| `null` | The checkout session the order came from. Defaults to the one the kit remembered when the session was created. Pass it explicitly when `useOrderConfirmation` runs on the same page: its completed poll forgets the remembered session. |

***

### UseTrackPurchaseResult

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="error-7" /> `error` | `Error` \| `null` | Why it could not be tracked, or null. Never thrown. |
| <a id="tracked" /> `tracked` | `boolean` | True once the purchase has been handed to analytics — by this hook, or by an earlier page load of this browser (the platform's `qb_purchase_tracked_{storeId}_{orderNumber}` key exists). Either way there is nothing left to do. |

## Type Aliases

### CartStatus

```ts theme={null}
type CartStatus = "idle" | "loading" | "ready" | "error";
```

***

### ShopkitAnalyticsOptions

```ts theme={null}
type ShopkitAnalyticsOptions = Omit<AnalyticsProviderProps, "children">;
```

What `consent={{ analytics }}` takes: the `<AnalyticsProvider>` props.

## Variables

### AnalyticsContext

```ts theme={null}
const AnalyticsContext: react.Context<Analytics | null>;
```

***

### ConsentContext

```ts theme={null}
const ConsentContext: react.Context<ConsentContextValue | null>;
```

***

### ShopkitContext

```ts theme={null}
const ShopkitContext: react.Context<ShopkitContextValue | null>;
```

## Functions

### AddToCart()

```ts theme={null}
function AddToCart(__namedParameters): ReactNode;
```

Add-to-cart bound to the selection, with its own disabled/pending state.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | `RenderProp`\<[`ProductAddToCartState`](#productaddtocartstate)> |

#### Returns

`ReactNode`

***

### AnalyticsProvider()

```ts theme={null}
function AnalyticsProvider(props): ReactNode;
```

Analytics and pixels for the tree below, gated on the shopper's consent.

Sits inside a shop context (it needs the client and the cart store) and
inside a consent provider (it reads the decision from there). One hub is created for the lifetime of the provider; the product,
search, cart, checkout and confirmation surfaces below it report into the
hub on their own, and `useAnalytics()` is for everything else.

```tsx theme={null}
<ConsentProvider>
  <ShopkitProvider config={config}>
    <AnalyticsProvider>{children}</AnalyticsProvider>
  </ShopkitProvider>
</ConsentProvider>
```

With `auto` (the default) the merchant's own GA4, GTM and Meta ids are read
from the shop and loaded — the same ids the hosted checkout loads, so the
storefront and the checkout report into the same properties. Nothing from
Google or Meta is loaded before the shopper decides: Google on `analytics`
(Consent Mode's denied default and the update pushed first), Meta on
`marketing`. `loadBeforeConsent` opts Google into loading earlier, in
Consent Mode denied.

`<ShopkitProvider>` mounts one of these by default (see its `consent`
prop), so most storefronts never render it themselves. Rendered inside a
tree that already has a hub, it joins that hub: its `destinations` are
added, and the pixels are not loaded a second time.

#### Parameters

| Parameter | Type |
| - | - |
| `props` | [`AnalyticsProviderProps`](#analyticsproviderprops) |

#### Returns

`ReactNode`

***

### Checkout()

```ts theme={null}
function Checkout(__namedParameters): Element;
```

The Quickbutik checkout, inside your own page.

```tsx theme={null}
<Checkout successUrl="/thanks" backUrl="/cart" onComplete={celebrate} />
```

Renders one `<div>` and lets `checkout.mount()` put the frame inside it. The
session is created once, from the props as they are at that moment: changing
`successUrl` afterwards does NOT rebuild the checkout, because doing so would
discard a session the shopper may be mid-payment in. The handlers are read
live, so passing an inline arrow costs nothing.

It follows the cart, exactly as `<qb-checkout>` does. While the cart is
still loading or has nothing in it, nothing is mounted and nothing is
reported as an error — `onEmpty` fires once, and the frame goes in when items
exist. Change the cart from the page while the frame is up — `useCart()`'s
`add`, a quantity stepper of your own — and the checkout re-reads it and
re-prices itself, so it never charges for a cart the shopper has already
moved on from. If the page instead empties the cart (`clear()`, the last line
removed) the frame comes down, because the session it shows names a cart that
no longer exists, and the next item added starts a fresh session.

A redirect payment method is handled for you, exactly as `<qb-checkout>`
handles it: when this page loads with `?qb_checkout_session=` and
`qb_checkout_shop=` on it — which is where the payment provider's return
bounces the shopper — the component RESUMES that session from those two
values, whatever state the cart is in (it is normally gone by then, because
the order exists), and takes the parameters back off the URL once the
resumed frame has answered. Nothing else in the page has to know. See
`docs/checkout.md`.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`CheckoutProps`](#checkoutprops) |

#### Returns

`Element`

***

### ConsentBanner()

```ts theme={null}
function ConsentBanner(props): ReactNode;
```

The cookie banner.

Headless first: hand it a render function and it renders exactly that,
with the state and the actions a banner needs — the choices as a `draft`,
`save()` to store them, `acceptAll()` / `rejectAll()` for the two buttons
every banner has.

```tsx theme={null}
<ConsentBanner>
  {({ visible, labels, draft, setDraft, acceptAll, rejectAll, save, open, openSettings }) => (
    <aside className="my-banner">
      <p>{labels.description}</p>
      {open ? (
        <label>
          <input type="checkbox" checked={draft.analytics}
                 onChange={(e) => setDraft({ analytics: e.target.checked })} />
          {labels.analytics}
        </label>
      ) : null}
      <button onClick={rejectAll}>{labels.rejectAll}</button>
      <button onClick={acceptAll}>{labels.acceptAll}</button>
    </aside>
  )}
</ConsentBanner>
```

Without children it renders a minimal, accessible dialog of its own — the
one place in the React layer that ships markup, because an empty consent
banner is a compliance bug, not a styling choice. It comes styled (a
stylesheet in `@layer qb-consent`, themed through `--qb-consent-*` custom
properties, light and dark), and every rule targets the stable
`qb-consent*` class names, so a site's own CSS for them wins; `unstyled`
leaves the stylesheet out. Accept-all and reject-all look the same, so
neither is more prominent unless you make it so. The copy comes from
`consentLabels(lang, labels)`.

Inside a `<ShopkitProvider>` that already shows the default banner, a
`<ConsentBanner>` of your own replaces it rather than adding a second one.

It renders nothing once the shopper has decided (unless `showWhenDecided`)
and reappears when something calls `openSettings()` or `reset()`. On a
server render without `initialState` on the provider it renders nothing
until hydrated, so a shopper who already decided never sees it flash.

#### Parameters

| Parameter | Type |
| - | - |
| `props` | [`ConsentBannerProps`](#consentbannerprops) |

#### Returns

`ReactNode`

***

### ConsentGate()

```ts theme={null}
function ConsentGate(__namedParameters): ReactNode;
```

Render something only once the shopper has allowed a category — a
third-party widget that sets its own cookies, a marketing embed.

```tsx theme={null}
<ConsentGate category="marketing" fallback={<p>Allow marketing cookies to see reviews.</p>}>
  <TrustpilotWidget />
</ConsentGate>
```

Undecided is denied, and so is a server render without `initialState`: the
fallback goes out first and the content appears once consent is known.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ConsentGateProps`](#consentgateprops) |

#### Returns

`ReactNode`

***

### ConsentProvider()

```ts theme={null}
function ConsentProvider(__namedParameters): ReactNode;
```

The shopper's cookie consent, for everything below it.

Independent of `<ShopkitProvider>`: a decision is per page, not per shop,
so this can sit above it (the usual place — the banner belongs to the page
shell) or below it. One store is created for the lifetime of the provider
and read from the `qb_consent` cookie in the browser.

```tsx theme={null}
<ConsentProvider revision={2}>
  <ShopkitProvider config={config}>
    <AnalyticsProvider>{children}</AnalyticsProvider>
  </ShopkitProvider>
  <ConsentBanner lang="sv" privacyPolicyUrl="/privacy" />
</ConsentProvider>
```

Rendering on the server? Read the cookie there and pass it down, so the
first byte already carries the right banner state and nothing flashes:

```tsx theme={null}
const consent = readConsentCookie(request.headers.get("cookie"), { revision: 2 })
<ConsentProvider revision={2} initialState={consent}>…</ConsentProvider>
```

Without `initialState` the server renders as undecided and the banner waits
for hydration before it shows anything, which is the flash-free fallback.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ConsentProviderProps`](#consentproviderprops) |

#### Returns

`ReactNode`

***

### ConsentSettingsButton()

```ts theme={null}
function ConsentSettingsButton(__namedParameters): ReactNode;
```

The "Cookie settings" button for a footer: reopens the banner's preferences
panel, so a shopper can change their mind (or withdraw) long after the
banner left. The React face of `<qb-consent-settings>`.

```tsx theme={null}
<footer>
  <ConsentSettingsButton className="link" />
  <ConsentSettingsButton>Ändra cookieval</ConsentSettingsButton>
</footer>
```

A real `<button type="button">`: opening a dialog is an action, not a
navigation, so keyboard and screen-reader users get the right element.
Every other button prop passes through; an `onClick` of your own runs
first and can `preventDefault()` to keep the panel shut. Renders nothing
without a consent provider above (`consent={false}` on the shop provider),
so a footer does not have to know whether consent is on.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ConsentSettingsButtonProps`](#consentsettingsbuttonprops) |

#### Returns

`ReactNode`

***

### JsonLd()

```ts theme={null}
function JsonLd(__namedParameters): ReactNode;
```

schema.org structured data as `<script type="application/ld+json">`.

Split out from `<SEO />` for the Next `generateMetadata` route, where the meta
tags come from `toNextMetadata()` and this is the piece that has nowhere else
to live:

```tsx theme={null}
const tags = buildSeo({ product, ...defaults })
return <><JsonLd data={tags.jsonLd} />…</>
```

Serialization escapes `<`, so a product name containing `</script>` cannot
break out of the tag.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`JsonLdProps`](#jsonldprops) |

#### Returns

`ReactNode`

***

### ProductConsumer()

```ts theme={null}
function ProductConsumer(__namedParameters): ReactNode;
```

Escape hatch: the entire product state in one call, for a layout that needs
several slices at once without nesting three render props.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | `RenderProp`\<[`ProductState`](#productstate)> |

#### Returns

`ReactNode`

***

### ProductImage()

```ts theme={null}
function ProductImage(__namedParameters): ReactNode;
```

A product image as an `<img>`, with the URL actually resolved.

This exists because `product.images[0].path` is **not** a URL — it is the bare
storage filename, and the shop's storage prefix that completes it is not
something a storefront credential can read. Rendering `path` directly is the
single most common way a headless Quickbutik shop ships broken images.

```tsx theme={null}
// Inside a <ProductProvider> — the main image, no props needed:
<ProductImage width={800} height={800} />

// In a product card, from a listing:
<ProductImage product={product} width={400} height={400} className="thumb" />

// A gallery:
{product.images.map((image) => (
  <ProductImage key={image.id} image={image} product={product} width={120} />
))}

// By id, anywhere under <ShopkitProvider> (fetches; needs <Suspense>):
<ProductImage productId="prod_27" width={64} height={64} />
```

Unlike the other components in this package it *does* render markup — a
single `<img>` and nothing around it. Every `<img>` attribute passes through,
so `className`, `style`, `sizes` and `onLoad` all work as usual. For a
different element (`next/image`, a CSS background) use
[useProductImage](#useproductimage) instead.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ProductImageProps`](#productimageprops) |

#### Returns

`ReactNode`

***

### ProductOptionGroup()

```ts theme={null}
function ProductOptionGroup(__namedParameters): ReactNode;
```

One named option group — for a layout that treats colour and size
differently (swatches vs. a size row) rather than looping uniformly.

Renders `fallback` when the product does not have that group, so the same
component tree works for a product without colours.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ProductOptionGroupProps`](#productoptiongroupprops) |

#### Returns

`ReactNode`

***

### ProductOptions()

```ts theme={null}
function ProductOptions(__namedParameters): ReactNode;
```

Every option group.

```tsx theme={null}
<ProductOptions>
  {(groups) => groups.map((group) => <MyGroup key={group.id} group={group} />)}
</ProductOptions>
```

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | `RenderProp`\<[`VariantOptionGroupState`](/kit/api/core#variantoptiongroupstate)\[]> |

#### Returns

`ReactNode`

***

### ProductOptionValues()

```ts theme={null}
function ProductOptionValues(__namedParameters): ReactNode;
```

Each value of one group, one call per value — the tightest form for a row of
cards or swatches. `children` receives the value state (including `selected`
and `available`); keying is the caller's job, as with any mapped output.

```tsx theme={null}
<ProductOptionValues option="Color">
  {(value) => <ColourCard key={value.id} value={value} />}
</ProductOptionValues>
```

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ProductOptionValuesProps`](#productoptionvaluesprops) |

#### Returns

`ReactNode`

***

### ProductPrice()

```ts theme={null}
function ProductPrice(__namedParameters): ReactNode;
```

Price for the current selection. `amount` is null before a variant resolves,
with `min`/`max`/`isRange` describing what is still reachable.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | `RenderProp`\<[`VariantPriceState`](/kit/api/core#variantpricestate) & `object`> |

#### Returns

`ReactNode`

***

### ProductProvider()

```ts theme={null}
function ProductProvider(__namedParameters): ReactNode;
```

Holds the variant selection for one product and derives everything from it.

Renders nothing of its own — no element, no styling, no markup. It is a
context provider and a state machine; the picker's appearance is entirely the
app's. Read it with the hooks below, or with the render-prop components in
`product-components`.

It also resolves the product for you, so a route can hand it a `slug`, an
`id`, or a promise instead of a loaded object:

```tsx theme={null}
// app/products/[slug]/page.tsx
export default async function Page({ params }) {
  const { slug } = await params
  return (
    <Suspense fallback={<Skeleton />}>
      <ProductProvider slug={slug} notFound={<NotFound />}>
        <SEO />
        <VariantPicker />
      </ProductProvider>
    </Suspense>
  )
}
```

```tsx theme={null}
// …or with the product already in hand:
<ProductProvider product={product} currency="SEK">
  <MyColourCards />   // useProductOptions()
  <MyPrice />         // useProductPrice()
  <MyAddButton />     // useProductAddToCart()
</ProductProvider>
```

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ProductProviderProps`](#productproviderprops) |

#### Returns

`ReactNode`

***

### SelectedVariant()

```ts theme={null}
function SelectedVariant(__namedParameters): ReactNode;
```

The resolved variant, or `fallback` while the selection is incomplete —
the natural place for "Select a size to continue".

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | `RenderProp`\<[`ProductVariant`](/kit/api/core#productvariant)> & `object` |

#### Returns

`ReactNode`

***

### SEO()

```ts theme={null}
function SEO(__namedParameters): ReactNode;
```

Every meta tag a Quickbutik page should have, from the product, category or
shop you hand it.

Inside a `<ProductProvider>` it needs nothing: the product and
currency come from that context, and the canonical from the `productPath`
template on `<SeoProvider>`.

```tsx theme={null}
<ProductProvider slug={slug}>
  <SEO />
</ProductProvider>

// Or explicitly, outside a product context:
<SEO product={product} url={`/products/${product.slug}`} />
<SEO category={category} url={`/categories/${category.slug}`} />
<SEO title="Sök" noIndex />
```

#### Where the tags end up

React 19 hoists `<title>`, `<meta>` and `<link>` into `<head>` from anywhere
in the tree, so this can be rendered inside the page component and the tags
still land in the right place. That is the intended setup.

On **React 18** there is no hoisting: the tags render inline where the
component sits. Crawlers do read `<meta>` in the body, but not reliably, and
`<title>` will not work at all — so on 18, use [useSeo](#useseo) with the
framework's own head mechanism instead.

`<script type="application/ld+json">` is never hoisted by React, and does not
need to be: Google reads JSON-LD anywhere in the document, body included.

#### In Next.js App Router

Either works. `generateMetadata` + `toNextMetadata()` is the idiomatic route
and gives Next control of the title — but Next's `Metadata` has no slot for
structured data, so pair it with `<JsonLd>` or the rich results are lost.
Rendering `<SEO />` alone emits both halves.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`SeoProps`](#seoprops) |

#### Returns

`ReactNode`

***

### SeoProvider()

```ts theme={null}
function SeoProvider(__namedParameters): ReactNode;
```

Site-wide SEO values, so a page only names what is specific to it.

Put it near the root with the things every page repeats — the storefront's
origin, the shop, the default share image:

```tsx theme={null}
<SeoProvider baseUrl="https://myshop.com" shop={shop} currency="SEK" organization>
  {children}
</SeoProvider>
```

Renders nothing of its own.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`SeoProviderProps`](#seoproviderprops) |

#### Returns

`ReactNode`

***

### ShopkitProvider()

```ts theme={null}
function ShopkitProvider(__namedParameters): ReactNode;
```

Makes one client and one shared cart store available to the tree, and, by
default, cookie consent with a banner and the shop's own analytics gated on
it (see the `consent` prop).

```tsx theme={null}
<ShopkitProvider config={{ publishableKey }} consent={{ lang: "sv", privacyPolicyUrl: "/integritet" }}>
  <App />
</ShopkitProvider>
```

Both are created once and kept for the lifetime of the provider: a client
rebuilt on every render would re-probe storage and, worse, hand every hook a
new identity on every render.

#### Parameters

| Parameter | Type |
| - | - |
| `__namedParameters` | [`ShopkitProviderProps`](#shopkitproviderprops) |

#### Returns

`ReactNode`

***

### useAnalytics()

```ts theme={null}
function useAnalytics(): Analytics;
```

The analytics hub. Throws outside an `<AnalyticsProvider>`.

```tsx theme={null}
const analytics = useAnalytics()
analytics.track(viewItemListEvent(items, "Nyheter"))
```

#### Returns

[`Analytics`](/kit/api/core#analytics)

***

### useAsync()

```ts theme={null}
function useAsync<T>(
   operation, 
   deps, 
options?): AsyncState<T>;
```

Minimal fetch-on-mount hook.

Intentionally NOT a cache: shopkit ships no data-layer opinion, so an app
using TanStack Query or SWR keeps its own cache and simply calls the client
directly. This exists for the common small case where pulling in a query
library for one product grid would be the heavier choice.

The fetch is aborted on unmount and on every re-run, and a resolved response
from a superseded run is discarded — so a fast-changing dependency (a search
box) cannot land an out-of-order result.

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Parameters

| Parameter | Type |
| - | - |
| `operation` | (`signal`) => `Promise`\<`T`> |
| `deps` | readonly `unknown`\[] |
| `options?` | \{ `enabled?`: `boolean`; `initialData?`: `T` \| `null`; } |
| `options.enabled?` | `boolean` |
| `options.initialData?` | `T` \| `null` |

#### Returns

[`AsyncState`](#asyncstate)\<`T`>

***

### useCart()

```ts theme={null}
function useCart(): UseCartResult;
```

The shopper's cart, shared across every component that calls this.

```tsx theme={null}
const { cart, itemCount, add, pending } = useCart()
<button onClick={() => add({ productId })} disabled={pending > 0}>Add</button>
```

The cart is loaded on first mount and never created speculatively — a visitor
who only browses causes no writes.

#### Returns

[`UseCartResult`](#usecartresult)

***

### useCategories()

```ts theme={null}
function useCategories(params?, options?): AsyncState<Page<Category>>;
```

One page of categories.

#### Parameters

| Parameter | Type |
| - | - |
| `params?` | `Omit`\<[`CategoryListParams`](/kit/api/core#categorylistparams), `"signal"`> |
| `options?` | \{ `enabled?`: `boolean`; `initialData?`: [`Page`](/kit/api/core#page)\<[`Category`](/kit/api/core#category)> \| `null`; } |
| `options.enabled?` | `boolean` |
| `options.initialData?` | [`Page`](/kit/api/core#page)\<[`Category`](/kit/api/core#category)> \| `null` |

#### Returns

[`AsyncState`](#asyncstate)\<[`Page`](/kit/api/core#page)\<[`Category`](/kit/api/core#category)>>

***

### useCheckout()

```ts theme={null}
function useCheckout(): UseCheckoutResult;
```

Hand the shopper off to the hosted Quickbutik checkout.

```tsx theme={null}
const { redirectToCheckout, starting } = useCheckout()
<button
  disabled={starting}
  onClick={() =>
    redirectToCheckout({
      successUrl: `${location.origin}/order`,
      backUrl: `${location.origin}/`,
    })
  }
>
  Till kassan
</button>
```

#### Returns

[`UseCheckoutResult`](#usecheckoutresult)

***

### useClientCurrency()

```ts theme={null}
function useClientCurrency(client): string | null;
```

The client's current currency, re-rendering when it changes. The server
snapshot is the configured default, so a server render and the first
client paint agree even when the browser has a remembered choice — the
client then re-renders with it.

#### Parameters

| Parameter | Type |
| - | - |
| `client` | [`ShopkitClient`](/kit/api/core#shopkitclient) \| `null` |

#### Returns

`string` | `null`

***

### useConsent()

```ts theme={null}
function useConsent(): UseConsentResult;
```

The consent decision and the actions on it. Throws outside a
`<ConsentProvider>`, so a missing provider fails at the component that
needs it rather than silently reading "undecided" forever.

```tsx theme={null}
const { allows, openSettings } = useConsent()
{allows("marketing") ? <TrustpilotWidget /> : null}
<button type="button" onClick={openSettings}>Cookie settings</button>
```

#### Returns

[`UseConsentResult`](#useconsentresult)

***

### useCurrency()

```ts theme={null}
function useCurrency(): UseCurrencyResult;
```

The currency the shop is browsed in, what the shop offers, and a setter —
everything a currency switcher needs.

```tsx theme={null}
function CurrencySwitcher() {
  const { currency, currencies, setCurrency } = useCurrency()
  if (currencies.length < 2) return null
  return (
    <select value={currency ?? ""} onChange={(e) => setCurrency(e.target.value)}>
      {currencies.map((c) => <option key={c.code} value={c.code}>{c.code}</option>)}
    </select>
  )
}
```

`mode` says what the choice means: `"display"` is shown converted and
charged in the shop's currency at checkout, `"charge"` is priced and
charged in it, `"base"` is the shop's own. The shop is read once per client
(`shop.get()`, which needs `checkout:read`) and shared by every caller.

#### Returns

[`UseCurrencyResult`](#usecurrencyresult)

***

### useOptionalAnalytics()

```ts theme={null}
function useOptionalAnalytics(): Analytics | null;
```

The hub when there is a provider above, null when there is not — for the
kit's own surfaces, which report when analytics is on and stay silent when
it is not.

#### Returns

[`Analytics`](/kit/api/core#analytics) | `null`

***

### useOptionalConsent()

```ts theme={null}
function useOptionalConsent(): UseConsentResult | null;
```

`useConsent()` when there is a provider above, null when there is not.

#### Returns

[`UseConsentResult`](#useconsentresult) | `null`

***

### useOptionalProductState()

```ts theme={null}
function useOptionalProductState(): ProductState | null;
```

The product state when there is a provider above, and null when there is not.

For components that enhance a product view but must also work outside one —
`<SEO />` reads this so a page inside `<Product>` needs no `product` prop,
while a page without one can still pass it explicitly.

#### Returns

[`ProductState`](#productstate) | `null`

***

### useOrderConfirmation()

```ts theme={null}
function useOrderConfirmation(sessionId?, options?): UseOrderConfirmationResult;
```

Poll a checkout session until its order exists — the thank-you page hook.

Order creation is asynchronous after payment, so a shopper can land here
before the order is written. This polls with the platform's own backoff and
settles on `completed`, `failed` or `timeout`.

Pass a session id, or omit it to use the one shopkit remembered when the
session was created.

```tsx theme={null}
const { status, orderNumber, loading } = useOrderConfirmation()
if (loading) return <Spinner label="Skapar din order…" />
if (status === "completed") return <ThankYou orderNumber={orderNumber} />
```

Took a snapshot on the server already (`shopkit.checkout.confirmation()` in
the page)? Pass it as `initialConfirmation`. A terminal one (`completed` or
`failed`) is the answer: nothing is polled, and the hook returns it from
the first render, so the page renders final without a spinner. Anything
else is where polling starts from.

Inside an analytics hub (on by default under `<ShopkitProvider>`) a
completed order is also reported as a `purchase` event, once per browser
(the platform's own dedup key), built from the session's lines and total,
whether it completed during the poll or arrived completed in
`initialConfirmation`. Not with `trackPurchase: false`, and not when the
checkout ran in `inline` success mode, where the embedded frame reports it
itself. The legacy checkout has no session to build from; call
`useAnalytics().trackPurchase()` yourself there.

#### Parameters

| Parameter | Type |
| - | - |
| `sessionId?` | `string` \| `null` |
| `options?` | [`UseOrderConfirmationOptions`](#useorderconfirmationoptions) |

#### Returns

[`UseOrderConfirmationResult`](#useorderconfirmationresult)

***

### useProduct()

```ts theme={null}
function useProduct(productId, options?): AsyncState<Product | null>;
```

A single product. `data` is null when it does not exist or is hidden.

Priced for the client's campaign storefront when it has one; pass
`storefrontId` to price it for another (or `null` for none) — see
`ProductReadOptions`. Priced in the client's currency (or `currency`), and
refetched when it changes.

#### Parameters

| Parameter | Type |
| - | - |
| `productId` | `string` \| `number` \| `null` \| `undefined` |
| `options?` | \{ `currency?`: `string` \| `null`; `initialData?`: [`Product`](/kit/api/core#product-1) \| `null`; `storefrontId?`: `string` \| `null`; } |
| `options.currency?` | `string` \| `null` |
| `options.initialData?` | [`Product`](/kit/api/core#product-1) \| `null` |
| `options.storefrontId?` | `string` \| `null` |

#### Returns

[`AsyncState`](#asyncstate)\<[`Product`](/kit/api/core#product-1) | `null`>

***

### useProductAddToCart()

```ts theme={null}
function useProductAddToCart(): ProductAddToCartState;
```

Add-to-cart wired to the current variant selection.

Exists because every storefront otherwise rewrites the same guard: a product
with options must not be addable until one variant is pinned, and the cart
needs the variant id rather than the product id.

#### Returns

[`ProductAddToCartState`](#productaddtocartstate)

***

### useProductImage()

```ts theme={null}
function useProductImage(options?): ProductImageState;
```

Resolve a product image to a ready-to-render `src` / `srcSet` / `alt`.

The hook twin of `<ProductImage />`, for a renderer this component cannot be
— `next/image`, a background-image, an og:image:

```tsx theme={null}
const { src, alt, width, height } = useProductImage({ width: 800 })
return src ? <Image src={src} alt={alt} width={width} height={height} /> : null
```

#### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`ProductImageOptions`](#productimageoptions) |

#### Returns

[`ProductImageState`](#productimagestate)

***

### useProductOption()

```ts theme={null}
function useProductOption(optionIdOrName): VariantOptionGroupState | null;
```

One option group by id or by name (case-insensitive), or null when the product
has no such group — so a component written for "Color" degrades quietly on a
product that has none.

#### Parameters

| Parameter | Type |
| - | - |
| `optionIdOrName` | `string` \| `number` |

#### Returns

[`VariantOptionGroupState`](/kit/api/core#variantoptiongroupstate) | `null`

***

### useProductOptions()

```ts theme={null}
function useProductOptions(): VariantOptionGroupState[];
```

The option groups to render as a picker. Empty for a simple product.

#### Returns

[`VariantOptionGroupState`](/kit/api/core#variantoptiongroupstate)\[]

***

### useProductPrice()

```ts theme={null}
function useProductPrice(): VariantPriceState & object;
```

Price for the current selection.

`amount` is null until a variant is resolved; `min`/`max`/`isRange` describe
what is still reachable, which is what a "from …" label should read.

#### Returns

[`VariantPriceState`](/kit/api/core#variantpricestate) & `object`

***

### useProducts()

```ts theme={null}
function useProducts(params?, options?): AsyncState<Page<Product>>;
```

One page of products.

```tsx theme={null}
const { data, loading } = useProducts({ limit: 12 })
data?.data.map((product) => …)
```

Priced in the client's currency (or `params.currency`), and refetched when
`setCurrency()` changes it. Format with `product.currency`.

#### Parameters

| Parameter | Type |
| - | - |
| `params?` | `Omit`\<[`ProductListParams`](/kit/api/core#productlistparams), `"signal"`> |
| `options?` | \{ `enabled?`: `boolean`; `initialData?`: [`Page`](/kit/api/core#page)\<[`Product`](/kit/api/core#product-1)> \| `null`; } |
| `options.enabled?` | `boolean` |
| `options.initialData?` | [`Page`](/kit/api/core#page)\<[`Product`](/kit/api/core#product-1)> \| `null` |

#### Returns

[`AsyncState`](#asyncstate)\<[`Page`](/kit/api/core#page)\<[`Product`](/kit/api/core#product-1)>>

***

### useProductSearch()

```ts theme={null}
function useProductSearch(params?, options?): AsyncState<Page<Product>>;
```

One page of a server-side filtered, sorted catalog search.

```tsx theme={null}
const { data, loading } = useProductSearch({ search: q, sortBy: "price", limit: 24 })
```

Every parameter is a primitive, so the dependency list below is plain value
equality — pass an inline object freely, it will not re-fetch on identity
alone. A superseded request is aborted, which is exactly the behaviour a
search-as-you-type box wants.

Inside an `<AnalyticsProvider>` a `search` event is reported once per
search term, when its results have landed — so a box that searches as the
shopper types reports the terms that were actually shown, not every
keystroke that was aborted on the way.

#### Parameters

| Parameter | Type |
| - | - |
| `params?` | `Omit`\<[`ProductSearchParams`](/kit/api/core#productsearchparams), `"signal"`> |
| `options?` | \{ `enabled?`: `boolean`; `initialData?`: [`Page`](/kit/api/core#page)\<[`Product`](/kit/api/core#product-1)> \| `null`; } |
| `options.enabled?` | `boolean` |
| `options.initialData?` | [`Page`](/kit/api/core#page)\<[`Product`](/kit/api/core#product-1)> \| `null` |

#### Returns

[`AsyncState`](#asyncstate)\<[`Page`](/kit/api/core#page)\<[`Product`](/kit/api/core#product-1)>>

***

### useProductState()

```ts theme={null}
function useProductState(): ProductState;
```

The whole product state. Throws outside a `<ProductProvider>`.

#### Returns

[`ProductState`](#productstate)

***

### useSelectedVariant()

```ts theme={null}
function useSelectedVariant(): ProductVariant | null;
```

The resolved variant, or null while the selection is incomplete.

#### Returns

[`ProductVariant`](/kit/api/core#productvariant) | `null`

***

### useSeo()

```ts theme={null}
function useSeo(input?): SeoTags;
```

The resolved head payload for a page, merged with the defaults in scope.

Use this when the framework wants the data rather than the tags — a TanStack
Start `head()`, an Astro layout, or a Next `generateMetadata` (with
`toNextMetadata`). `<SEO />` is this plus the rendering.

#### Parameters

| Parameter | Type |
| - | - |
| `input?` | [`SeoInput`](/kit/api/core#seoinput) & [`SeoDefaults`](/kit/api/core#seodefaults) |

#### Returns

[`SeoTags`](/kit/api/core#seotags)

***

### useSeoDefaults()

```ts theme={null}
function useSeoDefaults(): SeoDefaults;
```

The defaults in scope. Empty when there is no `SeoProvider` above.

#### Returns

[`SeoDefaults`](/kit/api/core#seodefaults)

***

### useShop()

```ts theme={null}
function useShop(options?): AsyncState<Shop>;
```

Shop name, logo, brand colour, default language, and its currencies
(`currency`, `currencies`). For a currency switcher, `useCurrency()` reads
the same data and adds the setter.

#### Parameters

| Parameter | Type |
| - | - |
| `options?` | \{ `initialData?`: [`Shop`](/kit/api/core#shop-2) \| `null`; } |
| `options.initialData?` | [`Shop`](/kit/api/core#shop-2) \| `null` |

#### Returns

[`AsyncState`](#asyncstate)\<[`Shop`](/kit/api/core#shop-2)>

***

### useShopkit()

```ts theme={null}
function useShopkit(): ShopkitClient;
```

The configured client. Throws a named error rather than returning null so a
missing provider fails at the offending component instead of somewhere
downstream where `client` is suddenly undefined.

#### Returns

[`ShopkitClient`](/kit/api/core#shopkitclient)

***

### useShopkitContext()

```ts theme={null}
function useShopkitContext(): ShopkitContextValue;
```

#### Returns

[`ShopkitContextValue`](#shopkitcontextvalue)

***

### useTrackPurchase()

```ts theme={null}
function useTrackPurchase(input): UseTrackPurchaseResult;
```

Report the `purchase` event from a thank-you page of your own.

`useOrderConfirmation` does this for you; reach for this when you confirm
the order some other way and only need the tracking. The purchase is built
from the checkout session (`cart_products`, `order_total`) and
deduplicated through the platform's own localStorage key, so a reloaded
page does not count the order twice.

```tsx theme={null}
const { tracked, error } = useTrackPurchase({ orderNumber, sessionId })
```

No `<AnalyticsProvider>` above, or no order number yet, and it does
nothing. A session the platform returns no order data for sets `error`;
call `useAnalytics().trackPurchase()` yourself in that case.

#### Parameters

| Parameter | Type |
| - | - |
| `input` | [`UseTrackPurchaseInput`](#usetrackpurchaseinput) |

#### Returns

[`UseTrackPurchaseResult`](#usetrackpurchaseresult)

## References

### Analytics

Re-exports [Analytics](/kit/api/core#analytics)

***

### AnalyticsDestination

Re-exports [AnalyticsDestination](/kit/api/core#analyticsdestination)

***

### AnalyticsDestinationContext

Renames and re-exports [AnalyticsContext](/kit/api/core#analyticscontext)

***

### AnalyticsOptions

Re-exports [AnalyticsOptions](/kit/api/core#analyticsoptions)

***

### CommerceEvent

Re-exports [CommerceEvent](/kit/api/core#commerceevent)

***

### CommerceItem

Re-exports [CommerceItem](/kit/api/core#commerceitem)

***

### ConsentCategory

Re-exports [ConsentCategory](/kit/api/core#consentcategory)

***

### ConsentChoice

Re-exports [ConsentChoice](/kit/api/core#consentchoice)

***

### ConsentLabels

Re-exports [ConsentLabels](/kit/api/core#consentlabels)

***

### ConsentSnapshot

Re-exports [ConsentSnapshot](/kit/api/core#consentsnapshot)

***

### ConsentState

Re-exports [ConsentState](/kit/api/core#consentstate-1)

***

### ConsentStore

Re-exports [ConsentStore](/kit/api/core#consentstore)

***

### ConsentStoreOptions

Re-exports [ConsentStoreOptions](/kit/api/core#consentstoreoptions)

***

### PurchaseEvent

Re-exports [PurchaseEvent](/kit/api/core#purchaseevent)


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