Skip to main content
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 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:

Save button

A save/unsave toggle on a product page, or inside a product card on a collection page.
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.
The full attribute set is in the elements reference.

Header badge and drawer trigger

A header icon that shows how many items the customer has saved and opens the drawer on click.
data-swish-bind="count" receives the live count. data-swish-when="has-saves" hides the badge while nothing is saved.

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.
There is no declarative element for a list of items yet, so this example has no custom element tab.
Start by loading the collection and logging it. This runs in any environment, including the browser console on your store.
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(), which shares one cache across the page.
  • Actions go through intents, 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.
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.

Drawer with list tabs

A drawer that shows the customer’s named lists, “Saved from cart”, and “Recently viewed” as tabs above one grid.
One signal holds the current list context. One items() collection follows it, so switching tabs never rebuilds the collection. lists() supplies the tab labels and itemCount() the “All” badge.
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.
The event bus fires for every save on the page.

Next

State

Every signal these examples read, with its properties and methods.

Elements

Attributes and child conventions for swish-save and swish-drawer.

Intents

Every action a custom UI can trigger.

Events

Every change a custom UI can react to.