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

# Examples

> Common storefront use cases built with the Swish SDK - custom elements first, state helpers and React hooks where you need more control.

Each example solves one storefront job in up to three ways:

* **Custom element** - wrap your own markup, no JavaScript. The SDK binds state to `data-swish-*` attributes. Start here on a Liquid theme.
* **State helper** - the same signals the elements bind, called from your own script. Use it when the markup is generated at runtime or no element covers the case yet.
* **React** - the [`@swishapp/react`](https://swish.app/docs/references/react/hooks) hooks, for Hydrogen and other headless storefronts.

The JavaScript samples assume `window.swish` exists. If your script can run before the SDK boots, wait for it once and put the samples inside:

```js theme={null}
document.addEventListener("swish-ready", () => {
  // samples go here
});
```

## Save button

A save/unsave toggle on a product page, or inside a product card on a collection page.

> [tab] Custom element
>
> `context="pdp"` reads the product from the page URL and follows SPA navigation. `context="product-card"` walks to the nearest product link in the surrounding card and follows its `href`. Children marked `data-swish-when` are shown only in the listed states.
>
> ```html theme={null}
> <!-- Product page -->
> <swish-save context="pdp">
>   <button type="button">
>     <span data-swish-when="unsaved">Save</span>
>     <span data-swish-when="saved,saving" hidden>Saved</span>
>   </button>
> </swish-save>
>
> <!-- Product card -->
> <swish-save context="product-card">
>   <button type="button">
>     <span data-swish-when="unsaved">Save</span>
>     <span data-swish-when="saved,saving" hidden>Saved</span>
>   </button>
> </swish-save>
> ```
>
> The full attribute set is in the [elements reference](https://swish.app/docs/references/sdk/elements.md#swish-save).

> [tab] State helper
>
> [`itemContext`](https://swish.app/docs/references/sdk/state#itemcontextsource) resolves the product, [`itemState`](https://swish.app/docs/references/sdk/state#itemstatecontext-options) tracks whether it is saved. One effect keeps the button in sync.
>
> ```js theme={null}
> const button = document.querySelector(".my-save-button");
>
> // On a product page. Inside a card, pass the card's product link instead.
> const context = swish.state.itemContext(window.location);
> const state = swish.state.itemState(context);
>
> swish.state.effect(() => {
>   button.textContent = state.value.saved ? "Saved" : "Save";
>   button.disabled = state.value.loading;
> });
>
> button.addEventListener("click", () => state.toggle());
> ```

> [tab] React
>
> ```tsx theme={null}
> import { useSwishItem } from "@swishapp/react";
>
> function SaveButton({ productId }: { productId: string }) {
>   const { saved, toggle, loading } = useSwishItem({ productId });
>
>   return (
>     <button type="button" onClick={toggle} disabled={loading}>
>       {saved ? "Saved" : "Save"}
>     </button>
>   );
> }
> ```

## Header badge and drawer trigger

A header icon that shows how many items the customer has saved and opens the drawer on click.

> [tab] Custom element
>
> `data-swish-bind="count"` receives the live count. `data-swish-when="has-saves"` hides the badge while nothing is saved.
>
> ```html theme={null}
> <swish-drawer view="home">
>   <button type="button">
>     Wishlist
>     <span data-swish-bind="count" data-swish-when="has-saves" hidden>0</span>
>   </button>
> </swish-drawer>
> ```

> [tab] State helper
>
> [`itemCount`](https://swish.app/docs/references/sdk/state#itemcount) is the signal behind the badge. Opening the drawer is an [intent](https://swish.app/docs/references/sdk/intents.md#viewhome).
>
> ```js theme={null}
> const trigger = document.querySelector(".wishlist-trigger");
> const badge = trigger.querySelector(".wishlist-count");
> const count = swish.state.itemCount();
>
> swish.state.effect(() => {
>   const { count: total, loading } = count.value;
>   badge.textContent = String(total);
>   badge.hidden = loading || total === 0;
> });
>
> trigger.addEventListener("click", () => swish.intents.invoke("view:home"));
> ```

> [tab] React
>
> ```tsx theme={null}
> import { useSwishItemCount, useSwishIntent } from "@swishapp/react";
>
> function WishlistTrigger() {
>   const { count } = useSwishItemCount();
>   const { invoke: openDrawer } = useSwishIntent("view:home");
>
>   return (
>     <button type="button" onClick={() => openDrawer()}>
>       Wishlist {count > 0 && <span className="badge">{count}</span>}
>     </button>
>   );
> }
> ```

## Custom wishlist page

A standalone page that renders every saved item, or the list a share link or `view:list` intent points at. Mount it at `routes.listDetailPagePath` (default `/apps/wishlist`) so share links and deep links land on it.

> [!NOTE]
>
> There is no declarative element for a list of items yet, so this example has
> no custom element tab.

> [tab] State helper
>
> Start by loading the collection and logging it. This runs in any environment, including the browser console on your store.
>
> ```js theme={null}
> const context = swish.state.listContext(window.location);
> const items = swish.state.items(context);
>
> swish.state.effect(() => {
>   const { items: saved, loading, totalCount, hasMore } = items.value;
>   if (loading) return;
>   console.log(`${totalCount} saved, more pages: ${hasMore}`);
>   console.log(saved); // [{ id, productId, variantId, quantity, createdAt }, ...]
> });
> ```
>
> Three things to know before rendering:
>
> * The effect runs again on every change to the collection - a save from the built-in drawer, an unsave from your page, a sign-in, or a call to `items.loadMore()` for the next page. Render from `items.value` and never keep your own copy.
> * Items carry Shopify IDs only. Resolve product data per card with [`product()`](https://swish.app/docs/references/sdk/state#productids), which shares one cache across the page.
> * Actions go through [intents](https://swish.app/docs/references/sdk/intents.md), so the built-in UI and your page stay in sync. The `unsave:item` intent removes an item, and the collection drops it when the `item-delete` event fires.
>
> The render below wires all three. It replaces the log with a card per item; rebuilding every card on each change is fine for a page because `product()` is cached, so no request repeats. Each card adds its image only once `product()` resolves with an image URL, because an `<img>` without a `src` shows as a broken image until then.
>
> ```js theme={null}
> const grid = document.querySelector(".wishlist-grid");
> const loadMore = document.querySelector(".wishlist-load-more");
>
> function renderCard(item) {
>   const card = document.createElement("article");
>   const image = document.createElement("img");
>   const title = card.appendChild(document.createElement("h3"));
>   const remove = card.appendChild(document.createElement("button"));
>   remove.textContent = "Remove";
>   remove.addEventListener("click", () =>
>     swish.intents.invoke("unsave:item", { itemId: item.id }),
>   );
>
>   const product = swish.state.product({
>     productId: item.productId,
>     variantId: item.variantId,
>   });
>   swish.state.effect(() => {
>     const { product: data, variant } = product.value;
>     if (!data) return;
>     title.textContent = data.title;
>     const url = variant?.image?.url ?? data.featuredImage?.url;
>     if (url) {
>       image.src = url;
>       card.prepend(image);
>     }
>   });
>
>   return card;
> }
>
> swish.state.effect(() => {
>   const { items: saved, loading, hasMore } = items.value;
>   if (loading) return;
>   grid.replaceChildren(...saved.map(renderCard));
>   loadMore.hidden = !hasMore;
> });
>
> loadMore.addEventListener("click", () => items.loadMore());
> ```
>
> Add to cart with `swish.intents.invoke("create:cart-line", { variantId: item.variantId })` and share the list with `swish.intents.invoke("initiate:share", { listId: context.value.listId })`. In a component framework that unmounts cards, call `product.dispose()` when the card goes away.

> [tab] React
>
> ```tsx theme={null}
> import { useSwishItems, useSwishIntent } from "@swishapp/react";
>
> function RemoveButton({ itemId }: { itemId: string }) {
>   const { invoke: unsave, pending } = useSwishIntent("unsave:item", { itemId });
>
>   return (
>     <button type="button" onClick={() => unsave()} disabled={pending}>
>       Remove
>     </button>
>   );
> }
>
> function WishlistPage() {
>   const { data: items, hasMore, loadMore, loading } = useSwishItems();
>
>   return (
>     <div>
>       {items.map((item) => (
>         <article key={item.id}>
>           <h3>{item.productId}</h3>
>           <RemoveButton itemId={item.id} />
>         </article>
>       ))}
>       {hasMore && (
>         <button type="button" onClick={loadMore} disabled={loading}>
>           Load more
>         </button>
>       )}
>     </div>
>   );
> }
> ```
>
> Resolve product data per card with [`swish.storefront.loadResolvedProduct`](https://swish.app/docs/references/sdk/storefront), or render from your own product data when the storefront already has it.

## Drawer with list tabs

A drawer that shows the customer's named lists, "Saved from cart", and "Recently viewed" as tabs above one grid.

> [tab] State helper
>
> One signal holds the current list context. One [`items()`](https://swish.app/docs/references/sdk/state#itemscontext-options) collection follows it, so switching tabs never rebuilds the collection. [`lists()`](https://swish.app/docs/references/sdk/state#listsoptions) supplies the tab labels and [`itemCount()`](https://swish.app/docs/references/sdk/state#itemcount) the "All" badge.
>
> ```js theme={null}
> const tabs = document.querySelector(".wishlist-tabs");
> const grid = document.querySelector(".wishlist-grid");
>
> const current = swish.state.signal({ listType: "all" });
> const items = swish.state.items(current);
> const lists = swish.state.lists();
> const count = swish.state.itemCount();
>
> function tab(label, badge, context) {
>   const button = document.createElement("button");
>   button.textContent = `${label} (${badge})`;
>   button.addEventListener("click", () => (current.value = context));
>   return button;
> }
>
> swish.state.effect(() => {
>   tabs.replaceChildren(
>     tab("All", count.value.count, { listType: "all" }),
>     ...lists.value.lists.map((list) =>
>       tab(list.name, list.itemCount, { listId: list.id, listType: list.type }),
>     ),
>   );
> });
>
> swish.state.effect(() => {
>   const { items: saved, loading } = items.value;
>   if (loading) return;
>   grid.replaceChildren(...saved.map(renderCard)); // from the wishlist page example
> });
> ```
>
> Smart lists come back from `lists()` with `type: "smart"`, so the same tab factory covers them.

## React to a save from anywhere

Run code when the customer saves an item, whether from your button, the built-in drawer, or a product card.

> [tab] Events
>
> The [event bus](https://swish.app/docs/references/sdk/events.md) fires for every save on the page.
>
> ```js theme={null}
> swish.events.subscribe("item-create", (event) => {
>   console.log("saved", event.detail.id, event.detail.productId);
> });
> ```

> [tab] State helper
>
> [`itemState`](https://swish.app/docs/references/sdk/state#itemstatecontext-options) tracks one product. Its `status` moves through `saving` to `saved`.
>
> ```js theme={null}
> const context = swish.state.itemContext(window.location);
> const state = swish.state.itemState(context);
>
> swish.state.effect(() => {
>   console.log(state.value.status); // "unsaved" -> "saving" -> "saved"
> });
> ```

## Next

> [card] [State](https://swish.app/docs/references/sdk/state.md)
>
> Every signal these examples read, with its properties and methods.

> [card] [Elements](https://swish.app/docs/references/sdk/elements.md)
>
> Attributes and child conventions for swish-save and swish-drawer.

> [card] [Intents](https://swish.app/docs/references/sdk/intents.md)
>
> Every action a custom UI can trigger.

> [card] [Events](https://swish.app/docs/references/sdk/events.md)
>
> Every change a custom UI can react to.


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