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

# Web components setup

> Use the kit's headless custom elements in any stack: with a bundler (Vite, Astro, Vue, Svelte, Nuxt) or with one script tag and no build step.

`@quickbutik/kit/elements` is the same headless composition as the [React layer](/kit/react/setup), as custom elements: `<qb-product>`, `<qb-cart>`, `<qb-checkout-button>` and friends. Use it for anything that is not React: a plain HTML page, Astro, Vue, Svelte, Nuxt, a Rails, Laravel or WordPress view, Webflow, an existing jQuery theme.

* **Light DOM, no shadow root.** Your stylesheet reaches everything.
* **No markup of their own.** The elements bind data into markup *you* wrote, through `data-qb-*` attributes, and reflect their state as attributes for your CSS. Four deliberate exceptions: `<qb-product-image>` renders an `<img>`, `<qb-cart-count>` writes its number, `<qb-seo>` writes into `<head>`, and `<qb-checkout>` renders the checkout's iframe.
* **No CSS shipped.** The elements reflect their state as attributes for your own stylesheet.

<Note>
  Do not mix the React layer and the elements on one page. Each brings its own cart store, and the two would not update together.
</Note>

## Choose a setup

<Tabs>
  <Tab title="Script tag (no build step)">
    One `<script>` with the key on it configures the shop and registers every element. The page below it is markup only: no init call, no wrapper element.

    ```html theme={null}
    <script
      defer
      src="https://cdn.jsdelivr.net/npm/@quickbutik/kit@1.8.0/dist/quickbutik-kit.global.js"
      data-publishable-key="qb_pk_…"
      data-currency="SEK"
      data-locale="sv-SE"></script>

    <a href="/cart">Varukorg (<qb-cart-count></qb-cart-count>)</a>
    ```

    * **Pin the version** in the URL and cache-bust on upgrade.
    * Use `defer`. The elements cope with being upgraded mid-parse either way; `defer` keeps the script off the critical path.
    * One self-contained file, no runtime dependencies, exposed as `window.Quickbutik`.

    Every attribute and the `window.Quickbutik` API are in [Scripting](/kit/web-components/scripting) and the [script tag reference](/kit/reference/script-tag).
  </Tab>

  <Tab title="With a bundler">
    ```bash theme={null}
    npm install @quickbutik/kit
    ```

    ```ts src/shop.ts theme={null}
    // Browser-only module: see "Import it only in the browser" below.
    import { configure, defineElements } from "@quickbutik/kit/elements"

    configure({
      publishableKey: import.meta.env.VITE_QUICKBUTIK_PUBLISHABLE_KEY,
      currency: "SEK",
      locale: "sv-SE",
    })
    defineElements()
    ```

    `configure()` takes every [`createShopkitClient`](/kit/reference/client) option plus `locale` (and `consent` / `analytics` options for the [cookie banner](#consent-and-analytics)), and returns `{ client, cartStore }`. `defineElements()` registers the tags; it is explicit (not an import side effect) so the entry stays tree-shakeable, and idempotent, so calling it twice or after the script tag already did is harmless.

    ```ts theme={null}
    defineElements({ prefix: "shop-" })                // <shop-product>, for a tag-name collision
    defineElements({ warnOnUnhandledActions: false })  // silence the typo warning for data-qb-action
    ```
  </Tab>
</Tabs>

<Warning>
  **Call `configure()` once, at startup.** A second call replaces the ambient client and cart store only for elements mounted afterwards; elements already on the page keep the first ones, so a badge and a drawer stop updating together. Never call it per client-side navigation.
</Warning>

## Import it only in the browser

`@quickbutik/kit/elements` defines classes that extend `HTMLElement` when it loads. Importing it in Node (an SSR entry, a frontmatter, a server file) throws `ReferenceError: HTMLElement is not defined`. The root entry `@quickbutik/kit` is the server-safe one; use it for server-side reads.

<CodeGroup>
  ```astro Astro theme={null}
  ---
  // Frontmatter runs on the server: use the plain client here, never /elements.
  import { createShopkitClient } from "@quickbutik/kit"
  const shopkit = createShopkitClient({ publishableKey: import.meta.env.PUBLIC_QUICKBUTIK_PUBLISHABLE_KEY })
  const { data: products } = await shopkit.products.list({ limit: 12 })
  ---
  <qb-cart-count></qb-cart-count>

  <script>
    // A <script> block is bundled for the browser.
    import { configure, defineElements } from "@quickbutik/kit/elements"
    configure({ publishableKey: import.meta.env.PUBLIC_QUICKBUTIK_PUBLISHABLE_KEY, currency: "SEK" })
    defineElements()
  </script>
  ```

  ```vue Vue theme={null}
  <script setup lang="ts">
  import { onMounted } from "vue"

  onMounted(async () => {
    const { configure, defineElements } = await import("@quickbutik/kit/elements")
    configure({ publishableKey: import.meta.env.VITE_QUICKBUTIK_PUBLISHABLE_KEY, currency: "SEK" })
    defineElements()
  })
  </script>

  <template>
    <qb-cart-count></qb-cart-count>
  </template>
  ```

  ```svelte Svelte / SvelteKit theme={null}
  <script lang="ts">
    import { onMount } from "svelte"

    onMount(async () => {
      const { configure, defineElements } = await import("@quickbutik/kit/elements")
      configure({ publishableKey: import.meta.env.VITE_QUICKBUTIK_PUBLISHABLE_KEY, currency: "SEK" })
      defineElements()
    })
  </script>

  <qb-cart-count></qb-cart-count>
  ```

  ```ts Nuxt (plugins/quickbutik.client.ts) theme={null}
  // The .client.ts suffix keeps this plugin out of the server bundle.
  import { configure, defineElements } from "@quickbutik/kit/elements"

  export default defineNuxtPlugin(() => {
    configure({ publishableKey: useRuntimeConfig().public.quickbutikKey, currency: "SEK" })
    defineElements()
  })
  ```
</CodeGroup>

<Tip>
  Tell your framework's template compiler that `qb-*` tags are custom elements, so it does not try to resolve them as components (Vue: `compilerOptions.isCustomElement = (tag) => tag.startsWith("qb-")`).
</Tip>

For pages that must be indexable, render the catalog HTML on the server with the plain client and use the elements for the interactive parts (cart, variant picker, checkout). See [Server-side rendering](/kit/vanilla/server-side).

## The ambient shop: there is no wrapper element

The shop is **ambient**: one client and one shared cart store per page, set up once by `configure()` or by the script tag's `data-publishable-key`. Every element finds it on its own. An element that talks to the API and finds no shop reports a named `ShopkitConfigError` listing the three ways to provide one, sets `state="error"` and emits `qb:error`.

### `<qb-shop>`: optional overrides

`<qb-shop>` exists for the cases a page-wide singleton cannot serve. A `<qb-shop>` ancestor wins for its own subtree; everything else keeps using the ambient shop.

```html theme={null}
<!-- Two shops on one page -->
<qb-shop publishable-key="qb_pk_a_…"> … </qb-shop>
<qb-shop publishable-key="qb_pk_b_…"> … </qb-shop>

<!-- One section selling into a campaign storefront -->
<qb-shop publishable-key="qb_pk_…" storefront-id="sf_01JBQ8ZK4M7XW9YR2TCVN3H5PD"> … </qb-shop>

<!-- A client you built yourself: custom fetch, per-request cookies -->
<qb-shop id="shop"> … </qb-shop>
<script type="module">
  document.getElementById("shop").client = myClient
</script>
```

| Attribute / property | |
| - | - |
| `publishable-key` | The shop's `qb_pk_…` |
| `api-url`, `checkout-url` | Only for a preview environment or a proxied checkout |
| `image-base-url` | Fallback image base; rarely needed |
| `currency` | The currency the subtree browses in. Changing it later switches that client without rebuilding it or its cart. See [Currencies](/kit/concepts/currencies) |
| `locale` | Number formatting for that subtree |
| `consent="false"`, `analytics="false"` | Turn consent, or only analytics, off for that subtree. A `<qb-shop>` for the same shop as the page reuses the page's consent store and analytics hub |
| `storage-key-prefix` | Run two shops in one browser |
| `storefront-id` | Bind the subtree to a [campaign storefront](/kit/concepts/campaign-storefronts). Changing it builds a new client and cart store |
| `.client` | A prebuilt client; wins over every attribute |
| `.initialCart` | A cart read on the server, so the first paint shows the real basket |
| `.cartStore` | The subtree's shared cart store |

It emits `qb:shop-ready` with `{ client, cartStore, currency, locale }`.

## Set the currency

Products and carts state the currency their prices are in (`product.currency`, `cart.currency`), and the elements format with it. For a shop that sells in its own currency only, nothing needs configuring.

`currency` in `configure()` (or `data-currency` on the script tag) is the currency the page **browses in**: it is sent with every product, cart and checkout request, and a shop that offers that currency answers with converted prices. Leave it out to start in the shop's own currency. It doubles as the formatting fallback for a platform older than `product.currency`.

```ts theme={null}
configure({ publishableKey, currency: "EUR", locale: "sv-SE" })
```

```html theme={null}
<script defer src="…/quickbutik-kit.global.js" data-publishable-key="qb_pk_…" data-currency="EUR" data-locale="sv-SE"></script>
```

For a shop that offers several currencies, drop in a switcher. A choice reprices every product, list and cart on the page (the same cart, re-read) and is remembered for the next visit, winning over the configured value:

```html theme={null}
<qb-currency-select></qb-currency-select>
```

Display versus charge currencies, `setCurrency()` and the switcher's templates are covered in [Currencies](/kit/concepts/currencies).

## Consent and analytics

**On by default.** A page configured by `configure()`, `<qb-shop>` or the script tag gets the kit's default cookie banner appended to `<body>` (unless it places its own `<qb-consent-banner>`), and loads the merchant's own GA4, GTM and Meta pixel from the shop's settings **only after the shopper agrees**. The elements report `view_item`, `search`, the cart events and the purchase, and every checkout handoff forwards the shopper's decision to the hosted checkout.

```ts theme={null}
configure({ publishableKey, consent: { lang: "sv", privacyPolicyUrl: "/integritetspolicy" } })
configure({ publishableKey, analytics: false })   // the banner, but no pixels
configure({ publishableKey, consent: false })     // no banner, no analytics
```

```html theme={null}
<script defer src="…/quickbutik-kit.global.js"
  data-publishable-key="qb_pk_…"
  data-consent-lang="sv"
  data-privacy-policy-url="/integritetspolicy"></script>

<!-- In the footer: reopens the consent dialog after the banner has gone -->
<qb-consent-settings lang="sv"></qb-consent-settings>
```

Your own banner markup, gated content and every option: [Consent and analytics](/kit/concepts/consent-and-analytics).

## Next steps

<CardGroup cols={2}>
  <Card title="Templates and bindings" icon="brackets-curly" href="/kit/web-components/templates">
    Bindings, repeats, actions and styling on state.
  </Card>

  <Card title="Build the storefront" icon="store" href="/kit/web-components/storefront">
    Catalog, product page, cart, checkout and thank-you page.
  </Card>

  <Card title="Scripting" icon="code" href="/kit/web-components/scripting">
    `window.Quickbutik`, events and recipes.
  </Card>

  <Card title="Elements reference" icon="book" href="/kit/reference/elements">
    Every element's attributes, scope, reflected state and events.
  </Card>
</CardGroup>


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