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

# React

> Every provider, hook and headless component exported from @quickbutik/kit/react.

<Info>
  This page is the curated reference. The complete, generated type reference lives at [/kit/api/react](/kit/api/react) and is regenerated from the published typings. For a walkthrough, start with [React setup](/kit/react/setup).
</Info>

```ts theme={null}
import { ShopkitProvider, useCart, ProductProvider } from "@quickbutik/kit/react"
```

Every export is a client hook or client component; the entry carries a `"use client"` directive. React is an optional peer dependency, `^18.2 || ^19`. The `slug`, `id` and promise forms of `<ProductProvider>`, and `<SEO />` tag hoisting, need React 19.

The components render **no markup of their own**: no wrapper, no class names. The exceptions are `<ProductImage />` (one `<img>`), `<Checkout>` (one `<div>` holding the frame), the SEO components (head tags), and the consent banner's default dialog and `<ConsentSettingsButton>` (which render only when you give them no content of your own).

## Providers and context

### ShopkitProvider

```tsx theme={null}
<ShopkitProvider config={{ publishableKey }} initialCart={cart}>{children}</ShopkitProvider>
<ShopkitProvider client={shopkit}>{children}</ShopkitProvider>
```

Makes one client and one shared [`CartStore`](/kit/reference/cart) available to the tree. Both are created once and kept for the provider's lifetime. By default it also mounts cookie consent, the default banner and the shop's own analytics, gated on the shopper's decision (see the `consent` prop).

<ParamField body="config" type="ShopkitConfig">A plain config object, which can cross a server-to-client boundary. Read once, not a render dependency. See [Client](/kit/reference/client).</ParamField>
<ParamField body="client" type="ShopkitClient">A client you built yourself. Takes precedence over `config`.</ParamField>
<ParamField body="initialCart" type="Cart | null">A cart read on the server. The store starts `ready`, so SSR and the first client paint agree.</ParamField>
<ParamField body="storefrontId" type="string | null">Sell the whole tree into one campaign storefront. Changing it rebuilds the client and the cart store. With a prebuilt `client`, it must match that client's binding or it throws.</ParamField>

<ParamField body="currency" type="string | null">
  Shorthand for `config.currency`: the currency to browse in until the shopper picks one, so a **default**, not an override. A later change switches the same client with `setCurrency()` without rebuilding it: catalog hooks refetch and the same cart is re-read. A change to `null` returns to the value the provider **mounted** with, not necessarily the shop's own currency; mount with no `currency` if `null` should mean the shop's own. With a prebuilt `client` the mount-time value is ignored (give the client its `currency`) and only later changes are forwarded. See [Currencies](/kit/concepts/currencies).
</ParamField>

<ParamField body="consent" type="ShopkitConsentOptions | false">
  Shapes the cookie consent and analytics the provider mounts, which are **on by default**. It does not switch them off: the one switch is `consent: false` in the client `config`, which a server client's checkout handoff reads too. `consent={false}` on the prop still turns consent off for this tree, but is **deprecated** and warns, because a server-started checkout cannot see it.

  <Expandable title="ShopkitConsentOptions">
    <ParamField body="initialState" type="ConsentState | null">The decision as the server read it (`await shopkit.consent.read()`), so the first paint shows the right banner state with no flash. Without it the banner waits for hydration.</ParamField>
    <ParamField body="lang" type="string | null">Language of the built-in copy (`en`, `sv`, `da`, `nb`, `fi`), for the banner and every `<ConsentSettingsButton>` below. Set it when rendering on the server.</ParamField>
    <ParamField body="privacyPolicyUrl" type="string | null">Adds a "Privacy policy" link to the banner.</ParamField>
    <ParamField body="labels" type="Partial<ConsentLabels>">Overrides for any of the banner's built-in strings.</ParamField>
    <ParamField body="banner" type="boolean | (state: ConsentBannerState) => ReactNode" default="true">`false` renders no banner (render your own with `<ConsentBanner>` or `useConsent()`); a function is rendered in the default banner's place.</ParamField>
    <ParamField body="unstyled" type="boolean">Leave out the default banner's stylesheet.</ParamField>
    <ParamField body="className" type="string">Extra class on the default banner's root.</ParamField>
    <ParamField body="analytics" type="boolean | AnalyticsProviderOptions" default="true">`false` keeps the banner and loads no pixel; an object takes the `<AnalyticsProvider>` options (extra `destinations`, `auto: false`, `requireConsent`, `loadBeforeConsent`, …).</ParamField>
    <ParamField body="store" type="ConsentStore">A store you built yourself; the store options are then ignored.</ParamField>
    <ParamField body="revision / cookieName / maxAgeDays / domain / sameSite / secure / persist" type="ConsentStoreOptions">The consent store's options. Prefer setting `revision` and `cookieName` once in `config.consent`, where the server read and the checkout handoff see them too.</ParamField>
  </Expandable>

  A provider nested inside another (a campaign provider inside the site's) reuses the outer consent store, banner and analytics hub. See [Consent and analytics](/kit/concepts/consent-and-analytics).
</ParamField>

<ParamField body="children" type="ReactNode" required />

### useShopkit / useShopkitContext

```ts theme={null}
useShopkit(): ShopkitClient
useShopkitContext(): { client: ShopkitClient; cartStore: CartStore }
```

Both throw a `ShopkitConfigError` when there is no provider above. `ShopkitContext` is exported for advanced composition.

## Cart

### useCart

```ts theme={null}
const { cart, itemCount, status, pending, error, revision, add, updateItem, removeItem, clear, clearCart, refresh } = useCart()
```

Every caller shares the same store. The cart is loaded on first mount and **never created speculatively**.

<ResponseField name="cart" type="Cart | null" />

<ResponseField name="itemCount" type="number">Sum of quantities, for a badge.</ResponseField>

<ResponseField name="status" type="'idle' | 'loading' | 'ready' | 'error'" />

<ResponseField name="pending" type="number">Mutations in flight.</ResponseField>
<ResponseField name="error" type="Error | null">Captured, not thrown.</ResponseField>
<ResponseField name="revision" type="number">Bumped once per successful mutation made through the store.</ResponseField>
<ResponseField name="add(item)" type="Promise<Cart | null>">`{ productId, variantId?, quantity? }`. `null` on failure.</ResponseField>
<ResponseField name="updateItem(itemId, quantity)" type="Promise<Cart | null>">Line id. `0` removes the line.</ResponseField>

<ResponseField name="removeItem(itemId)" type="Promise<Cart | null>" />

<ResponseField name="clear()" type="Promise<void>">Deletes the remembered cart and forgets it. `clearCart` is an alias.</ResponseField>

<ResponseField name="refresh()" type="Promise<Cart | null>" />

## Catalog hooks

All return `AsyncState<T>`: `{ data: T | null; loading: boolean; error: Error | null; refetch(): void }`. Each fetch is aborted on unmount and on every re-run, and a superseded response is discarded, so a search-as-you-type box cannot land an out-of-order result. They are not a cache; an app with TanStack Query or SWR should call the client directly.

### useProducts

```ts theme={null}
useProducts(params?: { limit?; cursor?; storefrontId?; currency? }, options?: { enabled?: boolean; initialData?: Page<Product> | null }): AsyncState<Page<Product>>
```

The product hooks follow the client's currency and **refetch when it changes**. A `currency` parameter prices one read in another currency (`null` for the shop's own).

### useProductSearch

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

Takes every [`products.search`](/kit/reference/products) parameter. All are primitives, so an inline object literal does not re-fetch.

### useProduct

```ts theme={null}
useProduct(productId: string | number | null | undefined, options?: { initialData?: Product | null; storefrontId?: string | null; currency?: string | null }): AsyncState<Product | null>
```

`data` is `null` for a hidden or missing product.

### useCategories

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

### useShop

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

## Currency

### useCurrency

```ts theme={null}
const { currency, selected, baseCurrency, currencies, mode, rate, chargeCurrency, setCurrency, isLoading, error } = useCurrency()
```

The currency the tree browses in, what the shop offers, and the switch. Reads the shop once per client (shared by every caller; needs `checkout:read`). See [Currencies](/kit/concepts/currencies).

<ResponseField name="currency" type="string | null">What prices are shown in: the choice when the shop offers it, else the shop's own.</ResponseField>
<ResponseField name="selected" type="string | null">What was asked for (the remembered choice, else the configured default), whether or not the shop offers it.</ResponseField>
<ResponseField name="baseCurrency" type="string | null">The shop's own currency.</ResponseField>
<ResponseField name="currencies" type="ShopCurrency[]">`{ code, mode, rate }[]`, base first. Only the base entry when the converter is off; empty while loading.</ResponseField>
<ResponseField name="mode" type="'base' | 'display' | 'charge'">`display`: shown converted, charged in the shop's currency. `charge`: priced and charged in it.</ResponseField>
<ResponseField name="rate" type="number">Units of `currency` per 1 unit of the shop's currency.</ResponseField>
<ResponseField name="chargeCurrency" type="string | null">What the checkout will charge.</ResponseField>
<ResponseField name="setCurrency(code)" type="Promise<void>">Switch and remember. Every product hook refetches, `useCart()` re-reads the same cart, and the next checkout opens in it. `null` returns to the default.</ResponseField>
<ResponseField name="isLoading" type="boolean">True while the shop, and with it the list of currencies, is loading.</ResponseField>

<ResponseField name="error" type="Error | null" />

```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>
  )
}
```

### useClientCurrency

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

The bare subscription: `client.currency`, re-rendering when it changes. No shop read.

### useAsync

```ts theme={null}
useAsync<T>(operation: (signal: AbortSignal) => Promise<T>, deps: readonly unknown[], options?: { enabled?; initialData? }): AsyncState<T>
```

The primitive the catalog hooks are built on, for a read of your own with the same abort semantics.

## Product and variant selection

### ProductProvider

```tsx theme={null}
<ProductProvider product={product}>…</ProductProvider>          // resolved
<ProductProvider product={promiseForProduct}>…</ProductProvider> // React 19 use()
<ProductProvider slug="cotton-tee">…</ProductProvider>           // looks it up
<ProductProvider id="prod_27">…</ProductProvider>                // one request
```

Holds the variant selection for one product and derives everything from it. Renders nothing of its own. A `slug` or `id` lookup re-reads the product when the client's currency changes. With analytics on, it tracks `view_item` once per product.

<ParamField body="product" type="Product | Promise<Product | null> | null">A resolved product, or a promise unwrapped with React 19 `use()` (wrap in `<Suspense>`). Takes precedence over `slug` and `id`.</ParamField>
<ParamField body="slug" type="string">Looks the product up. Walks the catalog; needs React 19 and a `<ShopkitProvider>`.</ParamField>
<ParamField body="id" type="string | number">Looks the product up by id. Needs React 19 and a `<ShopkitProvider>`.</ParamField>
<ParamField body="notFound" type="ReactNode">Rendered when the product is missing or hidden.</ParamField>
<ParamField body="initialVariantId" type="number">Preselect a variant, for `?variant=` deep links.</ParamField>
<ParamField body="selectFirstAvailable" type="boolean" default="false">Start on the first non-hidden variant instead of a price range.</ParamField>
<ParamField body="currency" type="string">A formatting fallback, used only when the product does not state its own `product.currency` (a platform older than the field). Passed through to `useProductPrice` and `<SEO />`.</ParamField>

<ParamField body="onVariantChange" type="(variant: ProductVariant | null) => void" />

<ParamField body="children" type="ReactNode" required />

### useProductState

```ts theme={null}
useProductState(): ProductState
useOptionalProductState(): ProductState | null // outside a provider
```

<ResponseField name="product" type="Product" />

<ResponseField name="options" type="VariantOptionGroupState[]">Each group with its values, `selected` and `available`.</ResponseField>

<ResponseField name="selection" type="Record<number, number>" />

<ResponseField name="selectedVariant" type="ProductVariant | null" />

<ResponseField name="isComplete" type="boolean">The selection pins one variant.</ResponseField>

<ResponseField name="hasOptions" type="boolean" />

<ResponseField name="price" type="VariantPriceState">`amount`, `compareAtAmount`, `min`, `max`, `isRange`.</ResponseField>
<ResponseField name="currency" type="string | undefined">The product's own `product.currency`, else the provider's `currency` prop. Format with this.</ResponseField>
<ResponseField name="select(optionId, valueId)" type="void">Repairs contradictions instead of refusing.</ResponseField>

<ResponseField name="clear(optionId)" type="void" />

<ResponseField name="reset()" type="void" />

<ResponseField name="selectVariant(variantId)" type="void" />

### Selection hooks

| Hook | Returns |
| - | - |
| `useProductOptions()` | `VariantOptionGroupState[]` |
| `useProductOption(idOrName)` | One group by id or name, or `null` |
| `useSelectedVariant()` | `ProductVariant \| null` |
| `useProductPrice()` | `VariantPriceState & { currency?: string }` |

### useProductAddToCart

```ts theme={null}
const { addToCart, canAddToCart, pending, error, missingOptions, buyNow, buying, buyNowError } = useProductAddToCart()
```

<ResponseField name="addToCart(quantity?)" type="Promise<Cart | null>">Adds the selected variant. A no-op while the selection is incomplete.</ResponseField>
<ResponseField name="canAddToCart" type="boolean">False until one variant is pinned, while a mutation is in flight, and during a buy-now.</ResponseField>

<ResponseField name="pending" type="number" />

<ResponseField name="error" type="Error | null" />

<ResponseField name="missingOptions" type="VariantOptionGroupState[]">The groups still missing a choice, for a "Select a size" prompt.</ResponseField>
<ResponseField name="buyNow(input, quantity?)" type="Promise<void>">Adds the selected variant and navigates to the hosted checkout. `input` is a `StartCheckoutInput`.</ResponseField>
<ResponseField name="buying" type="boolean">True while a buy-now runs, and after it navigated until the page is restored.</ResponseField>

<ResponseField name="buyNowError" type="Error | null" />

### Headless components

Each renders **only** what its `children` function returns.

| Component | Props | Child receives |
| - | - | - |
| `<ProductOptions>` | `children` | `VariantOptionGroupState[]` |
| `<ProductOptionGroup>` | `option` (id or name), `fallback?` | `VariantOptionGroupState` |
| `<ProductOptionValues>` | `option` (id or name), `fallback?` | `VariantOptionValueState`, once per value |
| `<SelectedVariant>` | `fallback?` | `ProductVariant` |
| `<ProductPrice>` | | `VariantPriceState & { currency? }` |
| `<AddToCart>` | | `ProductAddToCartState` (the `useProductAddToCart` result) |
| `<ProductConsumer>` | | `ProductState` |

`fallback` renders when the product has no such group, so a component written for "Color" degrades quietly.

```tsx theme={null}
<ProductOptionValues option={group.id}>
  {(value) => (
    <button key={value.id} aria-pressed={value.selected} aria-disabled={!value.available} onClick={() => select(group.id, value.id)}>
      {value.name}
    </button>
  )}
</ProductOptionValues>
```

## Images

### ProductImage

```tsx theme={null}
<ProductImage width={800} height={800} priority />          // inside <ProductProvider>
<ProductImage product={product} width={400} height={400} /> // a card
<ProductImage image={image} product={product} width={120} /> // a gallery
<ProductImage productId="prod_27" width={64} />              // fetches; needs <Suspense>
```

Renders a single `<img>` with the CDN URL and `srcSet` resolved. Product precedence: `image`, `product`, the surrounding `<ProductProvider>`, then a lookup by `productId` or `slug`.

<ParamField body="width" type="number">CSS pixels. Drives the resize, the `srcSet` and the box.</ParamField>

<ParamField body="height" type="number" />

<ParamField body="index" type="number" default="0">Which image, in display order.</ParamField>
<ParamField body="imageId" type="number">A specific image, for example `variant.imageId`.</ParamField>
<ParamField body="densities" type="number[]" default="[1, 2]">`[]` disables the `srcSet`.</ParamField>
<ParamField body="widths" type="number[]">`w` descriptors instead; pair with `sizes`.</ParamField>
<ParamField body="transform" type="ImageTransform">`quality`, `format`, `fit`, `crop` and so on.</ParamField>
<ParamField body="alt" type="string">Defaults to the image's alt text, then the product name, then `""`.</ParamField>
<ParamField body="fallback" type="ReactNode">Rendered when there is no image. Never a broken `<img>`.</ParamField>
<ParamField body="priority" type="boolean">`loading="eager"` and `fetchPriority="high"`, for the LCP image. Everything else lazy-loads.</ParamField>

<ParamField body="includePending" type="boolean" default="false" />

<ParamField body="cacheBust" type="boolean" default="true" />

<ParamField body="product / image / productId / slug" type="various">Which product and image; see above.</ParamField>

Every other `<img>` attribute passes through.

### useProductImage

```ts theme={null}
const { src, srcSet, alt, image, product, width, height } = useProductImage({ width: 800 })
```

The same resolution without rendering, for `next/image`, a CSS background or an og:image.

## Checkout

### useCheckout

```ts theme={null}
const { start, redirectToCheckout, buyNow, starting, error, url } = useCheckout()
```

<ResponseField name="start(input)" type="Promise<StartCheckoutResult | null>">Creates the session without navigating.</ResponseField>
<ResponseField name="redirectToCheckout(input)" type="Promise<void>">Creates the session and navigates with `location.assign`.</ResponseField>
<ResponseField name="buyNow(item, input)" type="Promise<void>">Adds one item to the remembered cart and navigates.</ResponseField>
<ResponseField name="starting" type="boolean">True while starting, and after a navigation until the page is restored from the back/forward cache. A navigation that never unloads releases it after 10 seconds.</ResponseField>

<ResponseField name="error" type="Error | null" />

<ResponseField name="url" type="string | null">The last handoff URL.</ResponseField>

A second click while one is in flight is ignored. `input` is a [`StartCheckoutInput`](/kit/reference/checkout); pass `cartId: cart?.id` from `useCart()` where cookies may be blocked (an app builder's preview).

### useOrderConfirmation

```ts theme={null}
const { status, orderNumber, outcome, confirmation, loading, error } = useOrderConfirmation(sessionId?, options?)
```

Polls until the order exists, the payment fails terminally, or the caps are reached. Without a `sessionId` it uses the one the kit remembered when the checkout started. With analytics on, a completed order is reported as the `purchase` event once, deduplicated across reloads.

<ParamField body="enabled" type="boolean" default="true">`false` never polls.</ParamField>
<ParamField body="initialConfirmation" type="SessionConfirmation | null">The snapshot the server already took with `shopkit.checkout.confirmation()`. A `completed` or `failed` one is final: nothing is polled, the bought cart and session are forgotten (as a completed poll does), and a completed one is reported as the `purchase`. A pending one is polled from.</ParamField>
<ParamField body="trackPurchase" type="boolean" default="true">Report the `purchase` event once the order exists.</ParamField>

```tsx theme={null}
// The server read a snapshot; a settled one renders final on the first paint.
const { status, orderNumber } = useOrderConfirmation(sessionId, { initialConfirmation })
```

<ResponseField name="status" type="'completed' | 'processing_payment' | 'no_attempt' | 'failed' | null" />

<ResponseField name="orderNumber" type="number | null" />

<ResponseField name="outcome" type="ConfirmationOutcome | null">`timeout` is not a failure.</ResponseField>

<ResponseField name="confirmation" type="SessionConfirmation | null" />

<ResponseField name="loading" type="boolean" />

<ResponseField name="error" type="Error | null">For example "no checkout session to confirm" on a direct visit.</ResponseField>

### Checkout

```tsx theme={null}
<Checkout successUrl="/success" backUrl="/cart" lang="sv" onComplete={({ orderNumber }) => track(orderNumber)} />
```

The hosted checkout rendered inline in an iframe. Renders one `<div>` with the frame inside. The session is created once, on mount; changing props afterwards does not rebuild it. It follows the cart: nothing mounts for an empty cart, and emptying the cart takes the frame down. See [Embedded checkout](/kit/concepts/embedded-checkout).

<ParamField body="successUrl" type="string">Relative URLs resolve against the page. Only the origin is used for the landing.</ParamField>

<ParamField body="backUrl" type="string" />

<ParamField body="lang" type="string" />

<ParamField body="theme" type="'light' | 'dark'">Omitted, the checkout follows the merchant's setting.</ParamField>

<ParamField body="confirmation" type="'redirect' | 'inline'" default="redirect" />

<ParamField body="minHeight" type="number" default="600" />

<ParamField body="className" type="string" />

<ParamField body="style" type="CSSProperties" />

<ParamField body="onReady" type="(detail: { sessionId, shopId, step }) => void" />

<ParamField body="onStep" type="(detail: { step }) => void" />

<ParamField body="onEvent" type="(detail: { name, params }) => void" />

<ParamField body="onComplete" type="(detail: { sessionId, orderNumber, successUrl }) => void" />

<ParamField body="onError" type="(detail: { code, message, fatal }) => void">A checkout that could not start is reported here, never thrown.</ParamField>

<ParamField body="onFallback" type="(detail: { reason, url }) => void" />

<ParamField body="onEmpty" type="() => void">Called once when the cart settles empty.</ParamField>

## Consent and analytics

`<ShopkitProvider>` mounts all of this by default; the pieces are public for a page that composes them itself. See [Consent and analytics](/kit/concepts/consent-and-analytics).

### ConsentProvider

```tsx theme={null}
<ConsentProvider revision={2} lang="sv">{children}</ConsentProvider>
```

Provides one consent store to the tree. Takes every `ConsentStoreOptions` field (`revision`, `cookieName`, `maxAgeDays`, `domain`, `sameSite`, `secure`, `persist`, `initialState`), plus `store` (a `ConsentStore` you built, for example one bridged to a third-party consent platform with `persist: false`) and `lang`. A `<ConsentProvider>` inside one that already exists shares the existing store: one decision per page.

### useConsent / useOptionalConsent

```ts theme={null}
const { status, analytics, marketing, open, allows, acceptAll, rejectAll, save, reset, openSettings, closeSettings, store } = useConsent()
useOptionalConsent() // the same, or null without a consent provider
```

<ResponseField name="status" type="'undecided' | 'decided'">Undecided is treated as denied.</ResponseField>

<ResponseField name="analytics / marketing" type="boolean" />

<ResponseField name="open" type="boolean">The preferences panel is open.</ResponseField>
<ResponseField name="allows(category)" type="boolean">`"necessary"` is always allowed.</ResponseField>

<ResponseField name="acceptAll() / rejectAll()" type="void" />

<ResponseField name="save(choice)" type="void">`{ analytics, marketing }`.</ResponseField>
<ResponseField name="reset()" type="void">Withdraw: forget the decision and show the banner again.</ResponseField>

<ResponseField name="openSettings() / closeSettings()" type="void" />

<ResponseField name="store" type="ConsentStore" />

`useConsent()` throws without a consent provider above.

### ConsentBanner

```tsx theme={null}
<ConsentBanner lang="sv" privacyPolicyUrl="/integritetspolicy" />   // the default accessible dialog
<ConsentBanner>{(state) => state.visible && <aside>…</aside>}</ConsentBanner>   // headless
```

With no children it renders the kit's styled, accessible dialog; with a render function it renders only what you return. A banner you place replaces the provider's default one.

<ParamField body="children" type="(state: ConsentBannerState) => ReactNode">`state` is the consent snapshot plus `visible`, `labels`, `draft`, `setDraft(partial)`, `acceptAll`, `rejectAll`, `save` (saves the draft), `reset`, `openSettings`, `closeSettings`.</ParamField>

<ParamField body="lang" type="string | null" />

<ParamField body="labels" type="Partial<ConsentLabels> | null" />

<ParamField body="privacyPolicyUrl" type="string | null" />

<ParamField body="className" type="string" />

<ParamField body="unstyled" type="boolean">Leave out the stylesheet.</ParamField>
<ParamField body="showWhenDecided" type="boolean">Keep it visible after a decision.</ParamField>

`visible` is true while undecided, while the preferences panel is open, or with `showWhenDecided`. The banner renders nothing before hydration unless the provider got `initialState`.

### ConsentSettingsButton

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

A real `<button type="button">` that reopens the banner's preferences, for a footer. Labelled "Cookie settings" in the provider's `lang` when given no children. Takes every `<button>` attribute plus `lang`.

### ConsentGate

```tsx theme={null}
<ConsentGate category="marketing" fallback={<p>Allow marketing cookies to see the video.</p>}>
  <iframe src="https://www.youtube.com/embed/…" />
</ConsentGate>
```

Renders `children` only once `category` is allowed, `fallback` until then.

### AnalyticsProvider

```tsx theme={null}
<AnalyticsProvider destinations={[tiktok]}>{children}</AnalyticsProvider>
```

The analytics hub, gated on the consent store above. `<ShopkitProvider>` mounts one by default; an explicit one inside it joins that hub (its `destinations` are added) instead of loading the pixels twice.

<ParamField body="destinations" type="AnalyticsDestination[]">Destinations of your own, on top of (or with `auto: false`, instead of) the shop's.</ParamField>
<ParamField body="auto" type="boolean" default="true">Load the merchant's GA4, GTM and Meta pixel from `shop.tracking`.</ParamField>
<ParamField body="requireConsent" type="boolean" default="true">`false` runs analytics ungated, and makes the hosted checkout track ungated too. Only for a storefront that owes its shoppers no consent.</ParamField>
<ParamField body="loadBeforeConsent" type="boolean" default="false">Load GTM or gtag.js before the shopper decides, under a Consent Mode default of everything denied. A GTM container loaded this way runs all of its tags.</ParamField>
<ParamField body="currency" type="string">The currency for events that carry none. Falls back to the cart's.</ParamField>
<ParamField body="storeId" type="string | number | null">The shop's numeric store id, for the purchase dedup key and Meta's `eventID`. Learned from the cart when omitted.</ParamField>
<ParamField body="bufferSize" type="number" default="50">Events remembered for destinations not ready yet. Minimum 1.</ParamField>
<ParamField body="debug" type="boolean">Log every dispatch.</ParamField>

### useAnalytics / useOptionalAnalytics

```ts theme={null}
const analytics = useAnalytics()           // throws without a hub
const maybe = useOptionalAnalytics()       // or null
analytics.track(viewItemListEvent(items, "New in"))
```

The `Analytics` hub. See [Utilities](/kit/reference/utilities#consent-and-analytics) for its methods and the event builders.

### useTrackPurchase

```ts theme={null}
const { tracked, error } = useTrackPurchase({ orderNumber, sessionId?, enabled?, affiliation? })
```

Reports the `purchase` for an order number you already have, from the checkout session's lines and total. Deduplicated per order across page loads, so it never double-counts beside `useOrderConfirmation`. Pass `sessionId` explicitly when `useOrderConfirmation` runs on the same page, because its completed poll forgets the remembered session. `tracked` is true once reported, here or on an earlier visit.

## SEO

See [SEO](/kit/concepts/seo).

### SeoProvider

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

Takes every `SeoDefaults` field: `baseUrl`, `siteName`, `titleTemplate`, `locale`, `defaultImage`, `twitterSite`, `twitterCreator`, `currency`, `productPath`, `categoryPath`, `shop`, `organization`. `useSeoDefaults()` reads them back.

### SEO

```tsx theme={null}
<SEO />                                         // inside <ProductProvider>
<SEO product={product} url={`/products/${product.slug}`} />
<SEO category={category} url="/categories/shirts" />
<SEO title="Tack för din order" noIndex noFollow />
```

Renders the title, meta tags, canonical, OpenGraph, Twitter card and JSON-LD. Takes every `SeoInput` and `SeoDefaults` field, plus `skipTitle` and `skipJsonLd`. React 19 hoists the tags into `<head>`; on React 18 use `useSeo()` with your framework's head.

### useSeo

```ts theme={null}
useSeo(input?: SeoInput & SeoDefaults): SeoTags
```

### JsonLd

```tsx theme={null}
<JsonLd data={tags.jsonLd} />
```

Renders `<script type="application/ld+json">` for one node or many. For Next.js `generateMetadata` pages, whose metadata has no JSON-LD slot.

## CartStore

`CartStore` and its `CartSnapshot` type are exported from this entry. See [Cart](/kit/reference/cart). So are `ConsentStore`, `Analytics` and the consent and analytics types (`ConsentState`, `ConsentSnapshot`, `ConsentCategory`, `ConsentChoice`, `ConsentLabels`, `AnalyticsDestination`, `CommerceEvent`, `CommerceItem`, `PurchaseEvent`), so a React app needs no second import.


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