> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/photoswipe.md).

# x-photoswipe

Directives and magics for the PhotoSwipe lightbox: slide data read from the DOM at open time, open buttons, and a slide counter.

Directives and magics for the PhotoSwipe lightbox: slide data read from the DOM at open time, open buttons, and a slide counter.

| Directive / attribute / magic        | What it does                                                                                                       |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `x-photoswipe`                       | The root. Loads PhotoSwipe lazily and initializes the lightbox with the theme's config (`window.theme.photoswipe`) |
| `x-photoswipe="{ maxZoomLevel: 2 }"` | Root with its own config expression, replacing the theme default                                                   |
| `x-photoswipe:button="id"`           | A button that opens the lightbox at the slide with that id                                                         |
| `x-photoswipe:counter`               | A `<template>` for the slide counter; `currentIndex` and `totalSlides` are in scope                                |
| `data-photoswipe-media="id"`         | Marks an `<img>`, a `<video>`, or a wrapper of one, as a slide                                                     |
| `data-photoswipe-json`               | On a `<script type="application/json">`: the body is a raw PhotoSwipe slide object                                 |
| `$photoswipe`                        | The PhotoSwipe lightbox instance (`null` until initialized)                                                        |
| `$photoswipeOpen(id)`                | Opens the lightbox at a slide id resolved at call time. Resolves to `false` when no lightbox exists                |

The PhotoSwipe library loads in an idle callback after the page settles, so the root adds nothing to the critical path. Slide data is collected fresh from the DOM each time the lightbox opens, in DOM order. Re-rendering a section doesn't produce duplicate slides. A button whose id matches no slide warns in the console and opens on the first slide. Focus trapping, focus return, and the control labels come from the config; the theme sets translated labels in `snippets/head-scripts.liquid`.

## Examples

### A basic lightbox

Use `data-photoswipe-media` to mark the image and `x-photoswipe:button` with the same id to open it:

```html
<div x-photoswipe>
  <img data-photoswipe-media="{{ media.id }}" src="…" width="1600" height="2000" alt="…">
  <button x-photoswipe:button="{{ media.id }}" aria-controls="photoswipe">Zoom</button>
</div>
```

The product gallery does this per media item: the zoom button in `product-media-zoom-button` opens the slide that `product-media-item` marked. The slide uses the image's own `src`, `srcset`, `width`, `height`, and `alt`, and the marked element doubles as the zoom-out target.

### Video slides

Use a `data-photoswipe-json` script when the element can't describe itself. A `<video>` tag has no width or height:

```html
<script type="application/json" data-photoswipe-json>
  {
    "id": "{{ media.id }}",
    "type": "video",
    "videoSrc": "{{ source.url }}",
    "msrc": "{{ media.preview_image | image_url }}",
    "width": {{ media.preview_image.width }},
    "height": {{ media.preview_image.height }},
    "alt": {{ media.alt | json }}
  }
</script>
```

The `photoswipe-video-json` snippet renders this structure. Place it anywhere inside the root; its position in the DOM decides its position in the slide order.

### A slide counter

Use an `x-photoswipe:counter` template to replace PhotoSwipe's built-in counter:

```html
<template x-photoswipe:counter>
  <div>
    <span x-text="currentIndex + 1"></span> / <span x-text="totalSlides"></span>
  </div>
</template>
```

The clone re-renders on every slide change. `core-section-wrapper` registers one counter template per section.

### Opening a slide resolved at click time

Use `$photoswipeOpen(id)` for one control that serves whichever slide is current:

```html
<button
  @click.stop.prevent="
    const fig = $el.closest('product-media-desktop')?.querySelector('figure[data-current=true]');
    if (fig) $photoswipeOpen(fig.dataset.photoswipeMedia);
  "
>
  Zoom
</button>
```

The product gallery's keyboard zoom button works this way. One focus-revealed button outside the slideshow opens the lightbox at the slide currently shown.

## Options

The config expression takes [PhotoSwipe's own options](https://photoswipe.com/options/). Without an expression, the root uses `window.theme.photoswipe`, which the theme fills with zoom limits, translated control labels, and the theme's icons in `snippets/head-scripts.liquid`.
