# MediaCarousel

> Unified image and adaptive-video carousel using the shared MediaItem array contract.

Canonical URL: https://fa7e86e3d553:3005/islands/content/media-carousel

> Render one image or video normally, or turn the same array into a navigable
> carousel with arrows, dots, touch swipe, autoplay, and hover previews.

## Preview

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

**Category**: Content

## Media Contract

`media` is always an array, including single-media use cases.

### Image

```json
{
  "type": "image",
  "src": "https://cdn.example.com/product-front.jpg",
  "alt": "Product front",
  "mobileSrc": "https://cdn.example.com/product-front-mobile.jpg",
  "fit": "cover",
  "objectPosition": "center"
}
```

### Adaptive Video

```json
{
  "type": "video",
  "poster": "https://cdn.example.com/product-poster.jpg",
  "alt": "Product demonstration",
  "sources": [
    { "url": "https://cdn.example.com/product.m3u8" },
    { "url": "https://cdn.example.com/product.mpd" },
    { "url": "https://cdn.example.com/product.mp4" }
  ],
  "captionsUrl": "https://cdn.example.com/product.vtt"
}
```

Source MIME types are inferred from `.m3u8`, `.mpd`, `.webm`, `.ogg`, and
common MP4-compatible extensions when omitted. Sources are attempted in order.
Inactive video slides render their poster instead of mounting hidden players.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `media` | `MediaItem[]` | Required | Ordered image/video array |
| `transition` | `"none" \| "fade" \| "slide" \| "zoom" \| "kenBurns"` | `"fade"` | Slide transition |
| `aspect` | string | `"1:1"` | CSS aspect ratio such as `"4:5"` or `"16:9"` |
| `fit` | `"cover" \| "contain"` | `"cover"` | Default media fit |
| `loop` | boolean | `true` | Wraps navigation at the first and last item |
| `autoplay` | boolean | `false` | Advances media on a timer |
| `interval` | number | `4000` | Autoplay interval in milliseconds |
| `pauseOnHover` | boolean | `true` | Pauses timed autoplay during pointer hover |
| `hoverAdvance` | boolean | `false` | Enables pointer-driven media changes |
| `hoverAdvanceMode` | `"next" \| "cycle"` | `"cycle"` | Holds the next item or continuously cycles |
| `hoverInterval` | number | `1200` | Cycle interval for hover mode |
| `navigation` | `"none" \| "arrows" \| "floatingArrows"` | Derived from `arrows` | Shared navigation contract |
| `arrows` | `"never" \| "always" \| "hover"` | Legacy boolean fallback | Arrow visibility |
| `dots` | `"never" \| "always" \| "hover"` | Legacy boolean fallback | Dot visibility |
| `swipe` | `"auto" \| "capture" \| "disabled"` | Legacy boolean fallback | Touch gesture ownership |
| `videoPlaybackMode` | `"manual" \| "active" \| "hover" \| "always-visible"` | `"active"` | Video playback policy |
| `videoSoundMode` | `"muted" \| "toggle" \| "hover"` | `"muted"` | Video sound policy |
| `videoControls` | `"never" \| "always" \| "hover"` | `"never"` | Video control visibility |
| `videoPreload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Active video preload policy |
| `videoLoop` | boolean | `true` | Loops active videos |

`showArrows` and `showDots` remain deprecated compatibility aliases.
`swipeable` remains a compatibility boolean for the richer `swipe` prop. New
source should use `navigation`, `dots`, and `swipe`.

Use `navigation:"arrows"` for persistent controls or
`navigation:"floatingArrows"` for controls that remain mounted and
keyboard-accessible while their visual presentation responds to hover/focus.

## Examples

### One Image

Controls are automatically omitted because the array contains one item.

```html
<lx-island name="MediaCarousel">
  <script type="application/json">
    {
      "media": [
        {
          "type": "image",
          "src": "https://cdn.example.com/product.jpg",
          "alt": "Product front"
        }
      ],
      "aspect": "4:5"
    }
  </script>
</lx-island>
```

### Next Image On Hover

```html
<lx-island name="MediaCarousel">
  <script type="application/json">
    {
      "media": [
        { "type": "image", "src": "https://cdn.example.com/front.jpg" },
        { "type": "image", "src": "https://cdn.example.com/back.jpg" }
      ],
      "hoverAdvance": true,
      "hoverAdvanceMode": "next",
      "navigation": "none",
      "dots": "never"
    }
  </script>
</lx-island>
```

### Continuous Hover Cycle

```html
<lx-island name="MediaCarousel">
  <script type="application/json">
    {
      "media": [
        { "type": "image", "src": "https://cdn.example.com/front.jpg" },
        { "type": "image", "src": "https://cdn.example.com/side.jpg" },
        { "type": "image", "src": "https://cdn.example.com/detail.jpg" }
      ],
      "hoverAdvance": true,
      "hoverAdvanceMode": "cycle",
      "hoverInterval": 900,
      "navigation": "floatingArrows",
      "dots": "always"
    }
  </script>
</lx-island>
```

### Mixed Image And HLS Video

```html
<lx-island name="MediaCarousel">
  <script type="application/json">
    {
      "media": [
        { "type": "image", "src": "https://cdn.example.com/front.jpg" },
        {
          "type": "video",
          "poster": "https://cdn.example.com/poster.jpg",
          "sources": [
            { "url": "https://cdn.example.com/demo.m3u8" },
            { "url": "https://cdn.example.com/demo.mp4" }
          ]
        }
      ],
      "navigation": "arrows",
      "dots": "always",
      "swipe": "capture",
      "videoSoundMode": "toggle",
      "videoControls": "hover"
    }
  </script>
</lx-island>
```

## Nested Carousels

Use `swipe:"capture"` when this carousel owns horizontal gestures inside a
product card or another horizontal rail. Use `swipe:"auto"` when the outer
container should retain gesture ownership.

## Motion and accessibility

- Previous and Next have accessible labels and 44px targets.
- Floating arrows remain mounted so keyboard users can reach them.
- One-item arrays render as normal media without redundant controls.
- Autoplay pauses on hover, keyboard focus, document hiding, and viewport exit.
- Manual navigation resets the timer instead of fighting the visitor.
- Reduced motion disables autoplay and animated transitions.

## CSS Customization

| Variable | Purpose |
|---|---|
| `--lx-media-carousel-radius` | Root radius |
| `--lx-media-carousel-bg` | Media background |
| `--lx-media-carousel-fit` | Image/video fit |
| `--lx-media-carousel-duration` | Transition duration |
| `--lx-media-carousel-arrow-size` | Arrow dimensions |
| `--lx-media-carousel-arrow-bg` | Arrow background |
| `--lx-media-carousel-arrow-color` | Arrow icon color |
| `--lx-media-carousel-arrow-radius` | Arrow shape |
| `--lx-media-carousel-dot-size` | Dot dimensions |
| `--lx-media-carousel-dot-color` | Inactive dot color |
| `--lx-media-carousel-dot-active-color` | Active dot color |

Stable parts include `root`, `viewport`, `scroll-container`, `track`, `slide`,
`image`, `video`, `controls`, `previous`, `next`, `dots`, and `dot`.

## Related Islands

- [ProductCarousel](/islands/commerce/product-carousel) - Product cards using this media contract
- [ProductGallery](/islands/commerce/product-gallery) - PDP thumbnail, collage, grid, masonry, and stacked layouts
- [FeaturedCollectionStage](/islands/commerce/featured-collection-stage) - Shared-stage collection showcase
- [VideoPlayer](/islands/content/video-player) - Standalone single-video presentation
