Skip to main content
@quickbutik/kit/react is a set of hooks and headless components over the same framework-free core as the rest of the kit. It renders no markup of its own (the one deliberate exception is <ProductImage />, which renders a single <img>), ships no CSS and adds no wrapper elements. Your app owns 100% of the DOM.
React is an optional peer dependency (^18.2 || ^19). Nothing else is installed: the kit has no runtime dependencies.
There is no data-fetching library underneath. State lives in a small observable store read through useSyncExternalStore. If your app already uses TanStack Query or SWR, see Using your own data layer.

Client components only

Every export from @quickbutik/kit/react is a client hook or client component; the entry point carries a "use client" directive. In a framework with server components (Next.js App Router, React Router RSC):
  • keep /react imports below a "use client" boundary, and
  • fetch catalog data on the server with the plain client from @quickbutik/kit, then hand it down.
The Next.js guide shows the full split.

<ShopkitProvider>

The provider makes one client and one shared cart store available to the tree. Every component that calls useCart() reads the same store, so a header badge and a cart page stay in sync without either owning the other.
The client and the cart store are created once and kept for the provider’s lifetime. config is read from a ref, not a memo dependency, so an inline object literal is fine. A config crosses the server/client boundary as a plain object (a client cannot be serialized), which is why server frameworks pass config rather than client.
ShopkitConfig
The same options as createShopkitClient. Only publishableKey is required.
ShopkitClient
A client you built yourself. Use instead of config.
Cart | null
A cart already read on the server. The store starts in ready with it, so server render and first client paint agree and the badge never flashes from 0 to 3.
string | null
Sell the whole tree into one campaign storefront. null or "" means not set.
string | null
Shorthand for config.currency: the currency to browse in until the shopper picks one. Changing it later switches the same client with setCurrency(). See Currencies.
Options for the cookie consent banner and the shop’s analytics, which are on by default: initialState (the server’s read, so the banner never flashes), lang, privacyPolicyUrl, labels, analytics, banner, unstyled and the consent store options. To turn consent off, set consent: false in the config, not here. See Consent.

Server-read cart: correct on first paint

Use cart.current() here, never ensure() or add(): current() reads and never creates, so a first-time visitor or a crawler causes no write.

Campaign storefronts

Product reads come back at campaign prices, carts are created bound to the campaign and the checkout is attributed to it.
  • Changing storefrontId rebuilds the client and the cart store. A campaign cart is priced differently and remembered under its own cookie, so moving between campaign pages must not keep the first campaign’s basket. Ids compare case-insensitively. initialCart applies to the first client only.
  • With a prebuilt client, the prop can only confirm that client’s binding. A client bound to a different campaign (or to none) throws a ShopkitConfigError. Build it with createShopkitClient({ storefrontId }), or pass config instead.
  • A config whose own storefrontId names a different campaign also throws, rather than silently picking one.
<ShopkitProvider> shows a cookie banner, keeps the shopper’s decision in a qb_consent cookie, and loads the merchant’s own GA4, GTM and Meta pixel (from the shop’s settings) only after the shopper agrees. The product page, search, the cart and the thank-you page report commerce events into it with no code of yours. <ConsentSettingsButton> reopens the dialog later.
consent={false} on the provider still turns consent off for that React tree, but it is deprecated and logs a warning: a server-started checkout cannot see a prop, so it would still forward a decision. Use consent: false in the config instead.
A provider nested inside another (a campaign storefrontId provider inside the site’s) reuses the outer consent store, banner and analytics hub. Everything else is in Consent and analytics.

Currencies

currency is the currency to browse in until the shopper picks one; leave it out to start in the shop’s own currency. useCurrency() lists what the shop offers and switches:
A switch reprices everything below the provider: catalog hooks refetch, useCart() re-reads the same cart, and the next checkout opens in the new currency. Nothing is rebuilt. The value at mount becomes the client’s default, so changing the prop to null later returns to that value rather than to the shop’s own currency. See Currencies for display and charge currencies and server rendering.

Reading the context

Both throw a named ShopkitConfigError when no provider is above, so the failure points at the offending component.

React 18 vs React 19

Everything works on React 18 except two things that rely on React 19:

Vite SPA (React, no server)

1

Scaffold and install

2

One client per app

src/main.tsx
In the browser, storage resolves to document.cookie, so the cart survives reloads.
3

Routes

Catalog with useProducts / useProductSearch, product pages with <ProductProvider slug> (React 19), checkout with useCheckout().redirectToCheckout, and a thank-you route at /success/:orderNumber with useOrderConfirmation().
4

SPA fallback on the host

The hosted checkout returns with a top-level GET to /success/<orderNumber>, so the host must serve index.html for unknown paths (Netlify/Cloudflare Pages _redirects: /* /index.html 200, Vercel rewrites, nginx try_files $uri /index.html).
A pure SPA is not reliably indexable. <SEO /> writes meta tags on React 19, but crawlers that do not run JavaScript see the empty shell. Prerender or render on a server when catalog SEO matters.

Using your own data layer

The hooks exist so a data library is not mandatory, not to replace one. With TanStack Query or SWR, call the client directly and keep ShopkitProvider only for useCart / useCheckout:
CartStore.hydrate(cart) adopts a cart fetched elsewhere, so badges and drawers built on useCart() update without a second request.

Next steps

Catalog and products

Hooks, ProductProvider and the variant picker.

Cart and checkout

useCart, useCheckout, inline checkout and the thank-you page.

Next.js App Router

The reference architecture with server components and server actions.

React API reference

Every hook and component.