> ## 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.

# Placements

> Reference for the Swish SDK Placement API - insert Swish buttons into your theme without editing template files.

Placements let you insert Swish elements (like Save and Home buttons) anywhere in your storefront. They use DOM observation to automatically mount when matching elements appear, and survive theme re-renders.

## Declarative placements

Pass a `placements` array to `createSwish()` and the SDK mounts them automatically at initialization. This is the recommended approach when you know which placements you need upfront.

```js theme={null}
const swish = await createSwish({
  storefront: { ... },
  context: { ... },
  placements: [
    {
      id: "card-save-overlay",
      target: { selector: ".product-card" },
      mount: { at: { query: ".product-card__image" }, mode: "append" },
      element: { name: "save-button" },
      layout: { type: "overlay", anchor: "top-right", offset: { x: 8, y: 8 } },
    },
    {
      id: "pdp-save-inline",
      target: { selector: ".product-form" },
      mount: { at: { query: 'button[type="submit"]' }, mode: "after" },
      element: { name: "save-button" },
      layout: { type: "inline" },
    },
  ],
});
```

Each entry in the array uses the same [placement spec](#placement-spec) documented below.

### `swish.placements.list`

After initialization, all declarative placements are available at `swish.placements.list`. Each entry is an object with `spec`, `mount`, and `unmount` properties, so you can control individual placements programmatically:

```js theme={null}
const overlay = swish.placements.list.find(
  (p) => p.spec.id === "card-save-overlay",
);
overlay.unmount();
```

## `swish.placements.create(spec)`

Creates a placement and returns an object with `mount()` and `unmount()` methods.

* `mount()` starts observing the DOM and inserts elements when targets are found.
* `unmount()` removes all inserted elements and stops observing.

## Placement spec

### `id`

**id** `string` *required*

Unique identifier for this placement. Used internally for deduplication,
stylesheet scoping, and DOM attributes.

### `target`

Defines which elements in the page the placement should attach to. The SDK uses a `MutationObserver` to watch for matching elements, including elements added after initial page load.

**target.selector** `string` *required*

CSS selector to match target elements.

**target.mustMatch** `boolean | ((element: Element) =\> boolean)`

Optional filter. Return `false` to skip a matched element. A plain boolean is
also accepted, so config-authored placements can serialize the switch without
a function.

**target.observerOptions** `IntersectionObserverInit`

If provided, the element is only mounted when it becomes visible in the
viewport. Accepts standard `IntersectionObserver` options.

### `mount`

Controls where the Swish element is inserted relative to the target.

**mount.mode** `string` *required*

Insertion mode. One of:

| Value | Behavior |
| - | - |
| `before` | Insert before the reference element. |
| `after` | Insert after the reference element. |
| `append` | Append as the last child of the reference element. |
| `prepend` | Prepend as the first child of the reference element. |
| `replace` | Replace the reference element entirely. |

**mount.at** `MountAt`

Determines the reference element for insertion. Defaults to the target element itself. Options:

| Value | Behavior |
| - | - |
| `"target"` | Use the target element directly. |
| `{ query: ".selector" }` | Query a descendant of the target. |
| `{ closest: ".selector" }` | Walk up from the target to find an ancestor. |
| `{ nearest: ".selector" }` | Walk up ancestors, querying each for the selector. Returns the closest match. |
| `{ withinClosest: { root: ".ancestor", selector: ".child" } }` | Find an ancestor, then query within it. |
| `".selector"` | Shorthand for `{ nearest: ".selector" }`. |

### `element`

The Swish element to insert.

**element.name** `string` *required*

Element type. Currently supports `"save-button"` and `"header-button"`.

**element.props** `Record\<string, unknown\>`

Attributes to set on the element. Keys are converted to kebab-case (e.g.
`productId` becomes `product-id`).

For `save-button`, supported props include `match: "variant" | "product" | "auto"` to control how saved state is interpreted. Use `match: "product"` for cards that show a default variant but should reflect product-level saved state. See [match options](https://swish.app/docs/references/sdk/state.md#options) for details.

`icon: "heart" | "bookmark" | "star"` picks this button's glyph. Leave it out to use the store's **Save button** icon style.

```js theme={null}
element: {
  name: "save-button",
  props: { match: "product" },
}
```

**element.classNames** `string[]`

CSS classes to add to the element.

Alternatively, pass a function that returns an `HTMLElement` for full control:

```js theme={null}
element: (spec) => {
  const el = document.createElement("div");
  el.textContent = "Custom element";
  return el;
},
```

### `style`

**style** `"on-image" | "on-page"`

Which look a `save-button` renders. **On image** is an icon-only chip for buttons over product media - by default a `#121212` heart on a white chip. **On page** is a heart with its label for buttons in the page flow - by default white on a `#121212` button, sized like your theme's buttons. Each look has its own [design tokens](https://swish.app/docs/guides/customize/appearance/design.md#save-button), and a look's tokens never affect the other.

A placement on a collection grid (`element.props.context: "product-card"`) is always On image, whatever `style` says. Otherwise the default is `"on-page"`. The SDK reflects the resolved look on the `<swish-ui>` host as `swish-save-style="on-image"` or `swish-save-style="on-page"`, so custom CSS can target one look.

```js theme={null}
style: "on-image",
```

A placed `save-button` also plays the store's [save animation](https://swish.app/docs/references/sdk/configuration.md#save-button). The SDK reflects it on the host as `swish-save-animation`, for example `swish-save-animation="pulse"`, and `"none"` when the store picked none.

### `layout`

Optional layout strategy for positioning the inserted element.

#### Overlay

Positions the element absolutely over the target's parent. Useful for floating a Save button on top of a product card image.

```js theme={null}
layout: {
  type: "overlay",
  anchor: "top-right",
  offset: { x: 8, y: 8 },
  zIndex: 10,
}
```

**layout.anchor** `string`

Corner to anchor to. One of `"top-left"`, `"top-right"`, `"bottom-left"`,
`"bottom-right"`. Defaults to `"bottom-right"`.

**layout.offset** `\{ x?: number, y?: number \}`

Pixel offset from the anchor corner. Supports responsive values.

**layout.zIndex** `number | string`

Custom z-index for the overlay: an integer (as a number or a string such as
`"20"`), `"auto"`, or a CSS-wide keyword such as `"inherit"`. Any other value
is ignored and the default `10` applies.

#### Inline

Wraps the reference element and the Swish element in a flex row, placing them side by side. Useful for adding a Save button next to an Add to Cart button.

```js theme={null}
layout: {
  type: "inline",
  rowSelector: ".existing-button-row",
}
```

**layout.rowSelector** `string`

If provided, the SDK uses an existing container as the flex row instead of
creating a wrapper. The selector is resolved via `closest()` from the
reference element.

#### Anchored

Positions the element relative to any element in the DOM, regardless of where the element is inserted. Unlike overlay, which requires the element to be a child of its positioning reference, anchored decouples DOM insertion from visual positioning. Use this when the element you want to overlay is a sibling or cousin of the mount target.

```js theme={null}
layout: {
  type: "anchored",
  to: { query: ".product-card__image" },
  placement: "top-end",
  inside: true,
  offset: 8,
  zIndex: 10,
}
```

**layout.to** `MountAt`

The element to visually anchor to, resolved from the target element. Accepts
the same values as `mount.at`. Defaults to the resolved mount target
(`renderTargetEl`).

**layout.placement** `string`

Where to position relative to the anchor element. Accepts any [Floating UI
placement](https://floating-ui.com/docs/computePosition#placement) string such
as `"top-start"`, `"top-end"`, `"bottom-start"`, `"bottom-end"`, `"top"`,
`"bottom"`, `"left"`, or `"right"`. Defaults to `"bottom-end"`.

**layout.inside** `boolean`

Position inside the anchor element's bounds rather than adjacent to it. When
`true`, the `placement` maps to corners within the anchor - for example,
`"top-end"` places the element at the top-right corner inside the anchor.
Defaults to `false`.

**layout.offset** `number`

Pixel offset from the anchor. When `inside` is `false`, this pushes the
element away from the anchor edge. When `inside` is `true`, this acts as an
inward inset from the corner.

**layout.zIndex** `number | string`

Custom z-index for the anchored element: an integer (as a number or a string
such as `"20"`), `"auto"`, or a CSS-wide keyword such as `"inherit"`. Any
other value is ignored. Defaults to `10`.

### `design`

Design tokens specific to the element type. Values are emitted as CSS custom properties scoped to the placement.

#### Save button design tokens

| Token | Type | Description |
| - | - | - |
| `background` | `string` | Default background color. |
| `savedBackground` | `string` | Background when the product is saved. |
| `color` | `string` | Default icon/text color. |
| `savedColor` | `string` | Color when the product is saved. |
| `iconFill` | `string` | Icon fill color. |
| `savedIconFill` | `string` | Icon fill color when the product is saved. |
| `backgroundSize` | `string \| number` | Diameter of an icon-only button. Numbers are treated as pixels. |
| `height` | `string \| number` | Button height. Numbers are treated as pixels. |
| `radius` | `string \| number` | Border radius. Numbers are treated as pixels. |

#### Home button design tokens

Left alone, the button is a 44px hit target around a 20px heart drawn in the header's text colour — the look of a theme's own header icons. The badge is centred on a corner of the heart icon itself, with no outline, so it stays on the heart when the button shows text: `statusIndicatorInset` picks the corner and the two offsets nudge from there.

| Token | Type | Description |
| - | - | - |
| `width` | `string \| number` | Button width. |
| `height` | `string \| number` | Button height. |
| `iconSize` | `string \| number` | Heart icon size. |
| `iconStrokeWidth` | `number` | Icon line weight. |
| `iconColor` | `string` | Icon color. |
| `iconMarginLeft` | `string \| number` | Space left of the icon. |
| `iconMarginRight` | `string \| number` | Space right of the icon. |
| `statusIndicatorBackground` | `string` | Badge color. |
| `statusIndicatorTextColor` | `string` | Badge text color. |
| `statusIndicatorFontSize` | `string \| number` | Badge text size. |
| `statusIndicatorFontWeight` | `string \| number` | Badge text weight. |
| `statusIndicatorSize` | `string \| number` | Badge diameter. |
| `statusIndicatorPadding` | `string \| number` | Badge padding. |
| `statusIndicatorBorderRadius` | `string \| number` | Badge corner radius. |
| `statusIndicatorOutline` | `string` | Badge outline, e.g. `2px solid #fff`. |
| `statusIndicatorInset` | `string` | Badge corner, as a CSS `inset` shorthand. Defaults to `0 0 auto auto` (top right). |
| `statusIndicatorOffsetX` | `string \| number` | Nudges the badge horizontally from that corner. |
| `statusIndicatorOffsetY` | `string \| number` | Nudges the badge vertically from that corner. |

```js theme={null}
design: {
  height: 40,
  iconSize: 24,
  statusIndicatorBackground: "#e11d48",
  statusIndicatorInset: "0 0 auto auto",
}
```

> [!NOTE]
>
> `background`, `color` and `radius` were removed — no header stylesheet read
> them, so they persisted and published while rendering nothing. Use
> `statusIndicatorBackground` for the badge and `iconColor` for the icon.

### `container`

Wraps the Swish element in an additional container element.

**container.tag** `keyof HTMLElementTagNameMap`

HTML tag for the container. Must be a valid HTML element tag name (e.g.,
`"div"`, `"span"`, `"section"`). Defaults to `"div"`.

**container.classNames** `string[]`

CSS classes to add to the container.

### `breakpoints`

Customize the responsive breakpoints used by `design` and `layout.offset` values. Defaults to `{ sm: 500, lg: 700 }`.

```js theme={null}
breakpoints: { sm: 480, md: 768, lg: 1024 }
```

### `css`

Raw CSS string appended after all generated styles. Use this as an escape hatch for styling that design tokens don't cover.

```js theme={null}
css: `[swish-placement-element="save-after-atc"] { opacity: 0.9; }`;
```

### `onMount` / `onUnmount`

Lifecycle callbacks invoked when the element is inserted into or removed from the DOM.

```js theme={null}
onMount: (ctx) => {
  console.log("Mounted on", ctx.targetEl);
  ctx.cleanup(() => {
    console.log("Cleaning up");
  });
},
onUnmount: (ctx) => {
  console.log("Unmounted from", ctx.targetEl);
},
```

The `MountContext` passed to `onMount` includes:

| Property | Type | Description |
| - | - | - |
| `id` | `string` | The placement ID. |
| `targetEl` | `Element` | The matched target element. |
| `renderTargetEl` | `Element` | The resolved reference element (from `mount.at`). |
| `mountEl` | `HTMLElement` | The inserted Swish element. |
| `containerEl` | `HTMLElement \| undefined` | The container wrapper, if configured. |
| `rowEl` | `HTMLElement \| undefined` | The inline row wrapper, if using inline layout. |
| `cleanup` | `(fn) => void` | Register a cleanup function called on unmount. |

## Responsive values

Any `design` token or overlay `offset` accepts responsive values. Pass an object with `base` and breakpoint keys:

```js theme={null}
design: {
  height: {
    base: 32,
    sm: 36,
    lg: 40,
  },
}
```

The `base` value applies at all screen sizes. Breakpoint values apply at their `min-width` and above.

## Ignoring elements

Add the `swish-ignore` attribute to any element to prevent placements from mounting on it:

```html theme={null}
<div class="product-card" swish-ignore>
  <!-- No placement will mount here -->
</div>
```

## Examples

### Overlay Save button on product cards

```js theme={null}
swish.placements
  .create({
    id: "card-save-overlay",
    target: { selector: ".product-card" },
    mount: {
      at: { query: ".product-card__image" },
      mode: "append",
    },
    element: { name: "save-button" },
    layout: {
      type: "overlay",
      anchor: "top-right",
      offset: { x: 8, y: 8 },
    },
    design: {
      background: "rgba(255, 255, 255, 0.9)",
      savedColor: "#e11d48",
      height: 36,
      radius: "50%",
    },
  })
  .mount();
```

### Anchored Save button on product images

Mounts the button into the product card, but visually positions it inside the top-right corner of the product image. This works even when the image and the mount point are siblings in the DOM.

```js theme={null}
swish.placements
  .create({
    id: "card-save-anchored",
    target: { selector: ".product-card" },
    mount: {
      mode: "append",
    },
    element: { name: "save-button" },
    layout: {
      type: "anchored",
      to: { query: ".product-card__image" },
      placement: "top-end",
      inside: true,
      offset: 8,
    },
    design: {
      background: "rgba(255, 255, 255, 0.9)",
      savedColor: "#e11d48",
      height: 36,
      radius: "50%",
    },
  })
  .mount();
```

### Inline Save button next to Add to Cart

```js theme={null}
swish.placements
  .create({
    id: "pdp-save-inline",
    target: { selector: ".product-form" },
    mount: {
      at: { query: 'button[type="submit"]' },
      mode: "after",
    },
    element: {
      name: "save-button",
      props: { variant: "primary" },
    },
    layout: { type: "inline" },
  })
  .mount();
```

### Lazy-mount on scroll

```js theme={null}
swish.placements
  .create({
    id: "lazy-save",
    target: {
      selector: ".product-card",
      observerOptions: { rootMargin: "200px" },
    },
    mount: {
      at: { query: ".product-card__actions" },
      mode: "append",
    },
    element: { name: "save-button" },
  })
  .mount();
```


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