# Events and components

URL: https://docs.sparklayer.io/developers/javascript-sdk/reference/events-and-components

SparkLayer's web components, such as spark-pdp and spark-product-card, and the DOM events they dispatch, with each event.detail and an example of each.

> **For AI assistants:** what to know before writing SparkLayer JavaScript SDK code.
>
> - **What it is:** `window.spark`, added to the storefront by the SparkLayer Core Script (version 4.x). On Shopify the SparkLayer app embed loads it; elsewhere it's `<script async src="https://sparkcdn.io/sparkjs/<SITE_ID>/<live|test>"></script>` in the theme's `<head>`. The old `cdn.sparklayer.io/spark.latest.js` and `spark.1.x.js` files are legacy: never use them.
> - **Options:** Configure it with `window.sparkOptions`, set before the Core Script loads. Extend the object rather than replacing it: `window.sparkOptions = { ...window.sparkOptions, … }`, or the Site ID and other snippets' hooks are lost. Headless only: `window.initSpark(options)` is defined when `window.sparkOptions` isn't set.
> - **When to call it:** Only after SparkLayer is ready: inside the `onReady(spark)` hook, or after a promise resolved from it. Most methods are async; prices and the cart are only available to signed-in B2B customers (check `await spark.isLoggedIn()`).
> - **Products and prices:** Products are looked up by the platform's parent product ID (on Shopify, `product.id`) and variants by SKU: `getPricingForVariant(parentProductId, sku)`, `calculatePricingForVariant(parentProductId, sku, qty)`, `getPackSizeForVariant(parentProductId, sku)`. For instant recalculation, fetch once with `getVariant` and call `calculatePricingForVariantData(variant, qty)`.
> - **Cart:** `updateCart({ products: [{ sku, adjustQuantity }] })` adds or removes relative to what's in the cart (also `customAttributes: [{ key, value }]`, `clearCart: true`). It resolves to the results, or `null` after showing its own error message; it doesn't throw. `getCart()` reads the cart; `openDrawer('cart')` opens it.
> - **Hooks:** `onCartUpdate(cart, results)`, `onCartLoad(cart)`, `onLogout()`, `preCartUpdateListener(input)` (async: return the possibly changed input) and `onCheckoutValidation(cart, cartUiState)` (return `[{ message }]` to block checkout, or `null`). When adding a hook, call any existing one first.
> - **Customising:** Build around SparkLayer or customise its own components, including their HTML. Prefer documented options, hooks, CSS variables and custom slots where they fit; keep direct HTML changes targeted and re-check them after SparkLayer updates.
> - **Docs:** The reference: methods https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md, hooks https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md, options https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md, types https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md (or the same types as a file: https://docs.sparklayer.io/developers/javascript-sdk/spark.d.ts). Setup and waiting for SparkLayer: https://docs.sparklayer.io/developers/javascript-sdk/setup.md. Use only what the reference documents.

SparkLayer's interfaces are web components: custom HTML elements you add to your theme. Some of them dispatch DOM events you can listen for, and you can dispatch one yourself to switch the drawer's tab.

## DOM events

| Event | When it fires | `event.detail` | Bubbles? |
| --- | --- | --- | --- |
| [`spark-variant-change`](#spark-variant-change) | The customer selects a variant in `spark-pdp` or `spark-product-card`. Dispatched on that element. | `{ product: Product; variant: ProductVariant }` | No: listen on each element. |
| [`spark-file-attachment-changed`](#spark-file-attachment-changed) | The list of files uploaded to a `spark-file-upload-field` changes. | `{ fileSize: number; fileName: string; gid: string }[] \| null` (`null` when every file is removed) | Yes, to `window`. |
| [`spark-file-attachment-failed`](#spark-file-attachment-failed) | A file fails to upload to a `spark-file-upload-field`. | `{ fileSize: number; fileName: string; errorCode: "unsupported-file-type" \| "file-too-large" \| "default" }` | Yes, to `window`. |
| [`view-update`](#view-update) | You dispatch it on `window` to switch an open drawer to a tab. | `{ view: DrawerUIState }` | You dispatch it with `bubbles: true`. |

### spark-variant-change

`event.detail.product` is a [`Product`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#product) and `event.detail.variant` a [`ProductVariant`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#productvariant); their `externalId` is your platform's ID. Add the listeners in `onReady`, once the elements are on the page.

```javascript title="sparkOptions"
window.sparkOptions = {
  ...window.sparkOptions,
  onReady: async () => {
    for (const card of document.querySelectorAll('spark-product-card')) {
      card.addEventListener('spark-variant-change', (event) => {
        console.log(event.detail.product.externalId, event.detail.variant.externalId);
      });
    }
  },
};
```

See [Update the product image when a variant is selected](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md#update-the-product-image-when-a-variant-is-selected).

### spark-file-attachment-changed

```javascript title="Product page"
window.addEventListener('spark-file-attachment-changed', (event) => {
  const gids = (event.detail ?? []).map((file) => file.gid);
  document.querySelector('#shirt-design').value = JSON.stringify(gids);
});
```

See [Let customers upload a file](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#let-customers-upload-a-file) to attach the files to the cart.

### spark-file-attachment-failed

```javascript title="Product page"
window.addEventListener('spark-file-attachment-failed', (event) => {
  const { fileName, errorCode } = event.detail;
  console.warn(`${fileName} wasn't uploaded: ${errorCode}`);
});
```

### view-update

`view` is a [`DrawerUIState`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#draweruistate). To open a closed drawer, use [`openDrawer()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#opendrawer).

```javascript title="Switch the open drawer to the Cart tab"
window.dispatchEvent(
  new CustomEvent('view-update', {
    bubbles: true,
    composed: true,
    detail: { view: 'cart' },
  }),
);
```

See [Open the cart drawer](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#open-the-cart-drawer).

## Web components

`parent-id` is your platform's ID for the parent product. For each interface's display settings (attributes such as `show-sku`), see [How products are shown](https://docs.sparklayer.io/help/storefront/product-display.md).

| Element | What it renders | Key attributes | See |
| --- | --- | --- | --- |
| `<spark-pdp>` | The product detail interface, on product pages. | `parent-id` | [Product detail](https://docs.sparklayer.io/help/storefront/interfaces/product-detail.md) |
| `<spark-product-card>` | The product card interface, wherever a product card appears. | `parent-id` | [Product card](https://docs.sparklayer.io/help/storefront/interfaces/product-card.md) |
| `<spark-product-matrix>` | Every variant in a grid, for products with two options. | `parent-id` | [Product matrix](https://docs.sparklayer.io/help/storefront/interfaces/product-matrix.md) |
| `<spark-product-price>` | The customer's price for a product, anywhere on the page. | `parent-id` | [Product price](https://docs.sparklayer.io/help/storefront/interfaces/product-price.md) |
| `<spark-product-rrp>` | The product's RRP. | `parent-id` | [Product price](https://docs.sparklayer.io/help/storefront/interfaces/product-price.md) |
| `<spark-file-upload-field>` | A file upload field for the product page. | `allowed-extensions` (a JSON array), `max-file-size` (bytes), `max-files`, `required` | [Let customers upload a file](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#let-customers-upload-a-file) |
| `<spark-drawer>` | The drawer (cart, account and other tabs). SparkLayer adds it when needed; use [`openDrawer()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#opendrawer) rather than adding it yourself. | – | [openDrawer()](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#opendrawer) |

```html title="Product page"
<spark-pdp parent-id="{{ product.id }}"></spark-pdp>

<spark-file-upload-field
  id="shirt-design-upload"
  allowed-extensions='["png", "jpg", "jpeg"]'
  max-files="1"
></spark-file-upload-field>
```

## Next steps

- [Events and analytics](https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics.md): Run your code from hooks and DOM events, and send B2B events to your analytics.
- [Products and pricing](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md): Swap the product image when a customer selects a variant.
- [Frontend integration](https://docs.sparklayer.io/developers/frontend.md): Add the product page interfaces and hide elements from B2B customers.
