# ProductGallery

> PDP media gallery with eight thumbnail, collage, grid, masonry, and stacked layouts.

Canonical URL: https://fa7e86e3d553:3005/islands/commerce/product-gallery

> Use for the primary PDP media area. The gallery keeps its specialized
> thumbnail and grid layouts while delegating media rendering and navigation to
> the shared MediaCarousel contract.

## Preview

> [Open the product-gallery interactive preview](https://storefront.trylexsis.com/v/island-preview-product-gallery-variations)

**Category**: Commerce

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `media` | `MediaItem[]` | `[]` | Canonical image/video array |
| `images` | array | - | Deprecated legacy alias normalized at the boundary |
| `layout` | `"horizontal" \| "vertical" \| "grid" \| "collageLeft" \| "collageRight" \| "twoColumn" \| "masonry" \| "stacked"` | `"horizontal"` | Gallery composition |
| `mobileLayout` | `"swipe" \| "stacked"` | Layout dependent | Mobile behavior for thumbnail and grid-family layouts |
| `enableLightbox` | boolean | Layout dependent | Enables the shared mixed-media lightbox |
| `autoplay` | boolean | `false` | Automatically advances the main pane |
| `interval` | number | `4000` | Autoplay interval |
| `transition` | `"none" \| "fade" \| "slide" \| "zoom" \| "kenBurns"` | `"fade"` | Main-pane transition |
| `navigation` | `"none" \| "arrows" \| "floatingArrows"` | `"none"` | Previous/Next control style for thumbnail layouts |
| `dots` | boolean | `false` | Show pagination dots for thumbnail layouts |
| `loop` | boolean | `true` | Wrap at the first and last media item |
| `swipe` | boolean | `true` | Enable touch swipe |
| `listenForVariant` | boolean | `false` | Responds to `variant:changed` media updates |
| `variantMediaMode` | `"focus" \| "filter" \| "replace"` | `"focus"` | Variant-media behavior |
| `maxHeight` | string | - | Main-pane maximum height |
| `thumbPosition` | `"bottom" \| "left" \| "right" \| "top"` | Layout dependent | Thumbnail placement |
| `objectFit` | `"cover" \| "contain"` | `"cover"` | Gallery media fit used when an item does not define `fit` or legacy `objectFit` |

## Mixed Media Example

```html
<lx-island name="ProductGallery">
  <script type="application/json">
    {
      "media": [
        {
          "type": "image",
          "src": "https://cdn.example.com/product-front.jpg",
          "alt": "Product front"
        },
        {
          "type": "image",
          "src": "https://cdn.example.com/product-side.jpg",
          "alt": "Product side"
        },
        {
          "type": "video",
          "poster": "https://cdn.example.com/product-poster.jpg",
          "sources": [
            { "url": "https://cdn.example.com/product.m3u8" },
            { "url": "https://cdn.example.com/product.mp4" }
          ],
          "alt": "Product demonstration"
        }
      ],
      "layout": "vertical",
      "thumbPosition": "left",
      "navigation": "floatingArrows",
      "dots": true,
      "objectFit": "contain"
    }
  </script>
</lx-island>
```

Video thumbnails remain posters. Only the active main-pane video mounts the
adaptive player.

## Layouts

| Layout | Composition |
|---|---|
| `horizontal` | Main viewer with thumbnails below by default |
| `vertical` | Main viewer with a left thumbnail rail by default |
| `grid` | Featured first media followed by equal two-column tiles |
| `collageLeft` | Large first media on the left with two stacked items on the right |
| `collageRight` | Mirrored collage with the first media on the right |
| `twoColumn` | Equal two-column media tiles |
| `masonry` | Variable tile rhythm using alternating aspect ratios |
| `stacked` | Full-width vertical editorial media sequence |

For either collage, the first three items create the opening composition.
Additional items continue as equal half-width tiles. Arrays containing one or
two items collapse naturally without blank cells.

### Collage Example

```html
<lx-island name="ProductGallery">
  <script type="application/json">
    {
      "media": [
        {
          "type": "image",
          "src": "https://cdn.example.com/product-front.jpg",
          "alt": "Product front"
        },
        {
          "type": "image",
          "src": "https://cdn.example.com/product-detail.jpg",
          "alt": "Product detail"
        },
        {
          "type": "video",
          "poster": "https://cdn.example.com/product-video.jpg",
          "sources": [
            { "url": "https://cdn.example.com/product.m3u8" }
          ],
          "alt": "Product demonstration"
        }
      ],
      "layout": "collageLeft",
      "mobileLayout": "swipe",
      "enableLightbox": true
    }
  </script>
</lx-island>
```

## Responsive Behavior

- Grid-family layouts default to `mobileLayout:"swipe"`, producing a
  one-item-plus-peek horizontal rail.
- `stacked` defaults to `mobileLayout:"stacked"`.
- Set `mobileLayout:"stacked"` on any grid layout for a single vertical column.
- Thumbnail layouts use a bounded square swipe frame by default and move
  left/right thumbnail rails into a horizontally scrollable row.
- Set `--lx-product-gallery-mobile-ratio` to change the mobile frame ratio.
  The safe default is `1 / 1`.
- Gallery roots, media frames, tracks, slides, pictures, images, and thumbnail
  strips are constrained to the available width and do not widen the page.

Media fit resolves in this order:

1. The media item's `fit` or backwards-compatible `objectFit`.
2. The gallery-level `objectFit`.
3. `cover`.

Use `contain` for packshots that must remain completely visible. Use `cover`
when intentional edge cropping is acceptable.

Grid-family layouts enable the lightbox by default. Thumbnail layouts leave it
off unless `enableLightbox:true` is provided, in which case an explicit expand
button appears over the main viewer.

## Variant Synchronization

With `listenForVariant:true`, the gallery responds to:

```ts
{
  variantId: string;
  title: string;
  price: string;
  available: boolean;
  image?: string;
  mediaId?: string;
  mediaIds?: string[];
  images?: Array<{
    id?: string;
    url: string;
    variantId?: string;
    variantIds?: string[];
  }>;
}
```

Media can be associated through its URL, `id`, `variantId`, or `variantIds`.
Multiple media items may belong to one variant.

- `focus` retains the complete original gallery and selects the first match.
- `filter` displays every original media item mapped to that variant.
- `replace` uses the event's replacement images.
- A missing match leaves the current media and index unchanged.

None of these modes mutate the authored `media` array.

## Interaction

- Thumbnail selection controls the shared carousel index.
- Touch swipe is captured by the main pane.
- Arrow-key navigation is provided by the shared media primitive.
- Grid tiles open the lightbox at the selected media index.
- The lightbox traps focus, closes with Escape or backdrop selection, restores
  focus to the trigger, and mounts video only for the active slide.
- Autoplay pauses and resets through the same state machine as standalone media.
- Autoplay pauses during pointer hover, keyboard focus, document hiding, and
  viewport exit, and resumes predictably after interaction.
- Reduced motion disables autoplay and animated transitions.
- Cached images use `decode()` and intrinsic dimensions to avoid hydration-time
  blank slides.

Carousel controls apply only to `horizontal` and `vertical` layouts. Grid,
collage, masonry, and stacked layouts reject carousel-only props during
validation instead of silently ignoring them.

## CSS Customization

| Part | Description |
|---|---|
| `root` | Gallery layout root |
| `thumbnail-strip` | Thumbnail rail |
| `thumbnail` | Individual thumbnail control |
| `main-media` | Main carousel container |
| `previous`, `next`, `controls`, `dots`, `dot` | Shared navigation controls |
| `open-lightbox` | Thumbnail-layout expand action |
| `grid` | Grid, collage, masonry, or stacked media container |
| `grid-item` | Selectable media tile |
| `media` | Tile media wrapper |
| `lightbox`, `lightbox-content`, `lightbox-close` | Expanded media viewer |
| `slide` | Shared media slide |
| `image`, `video` | Shared media renderers |

Useful gallery variables:

| Variable | Purpose |
|---|---|
| `--lx-product-gallery-gap` | Space between tiles |
| `--lx-product-gallery-columns` | Equal-grid and masonry column count |
| `--lx-product-gallery-tile-ratio` | Default tile aspect ratio |
| `--lx-product-gallery-featured-ratio` | Featured `grid` tile ratio |
| `--lx-product-gallery-stacked-ratio` | Stacked media ratio |
| `--lx-product-gallery-radius` | Tile corner radius |
| `--lx-product-gallery-bg` | Tile background |
| `--lx-product-gallery-mobile-peek` | Width of each mobile swipe item |
| `--lx-product-gallery-mobile-ratio` | Mobile swipe frame ratio; defaults to `1 / 1` |
| `--lx-product-gallery-thumbnail-size` | Thumbnail width and height; defaults to `64px` |
| `--lx-media-lightbox-backdrop` | Lightbox backdrop |
| `--lx-media-lightbox-max-width` | Lightbox content width |

The main pane also accepts all
[MediaCarousel CSS variables](/islands/content/media-carousel#css-customization).

## Related Islands

- [MediaCarousel](/islands/content/media-carousel) - Shared renderer and contract
- [VariantSwatches](/islands/commerce/variant-swatches) - Emits variant changes
- [OptionResolver](/islands/commerce/option-resolver) - Resolves multi-axis variants
- [BuyBox](/islands/commerce/buy-box) - Standard PDP companion
