# ShoppableVideoFeed

> Responsive shoppable video reels, rails, and grids with tagged Shopify products, adaptive streaming, and cart actions.

Canonical URL: https://0.0.0.0:3005/islands/commerce/shoppable-video-feed

> Build shoppable UGC, creator demonstrations, testimonials, and product-video
> sequences. One authored item list adapts across desktop and mobile.

## Preview

> [Open the shoppable-video-feed interactive preview](https://storefront.trylexsis.com/v/shoppable-video-all-variations)

**Category**: Commerce

## Variations

| Configuration | Props | Behavior |
|---|---|---|
| Paged reel | `variant:"reel"`, `reelMode:"paged"` | One active portrait video. Buttons on desktop and swipe on mobile |
| Vertical swipe reel | `variant:"reel"`, `reelMode:"vertical-scroll"` | Native vertical scrolling with mandatory snap and active-only autoplay |
| Auto rail | `variant:"rail"`, `autoAdvance:true` | Timed horizontal advancement without controls or navigation buttons |
| Hover rail | `variant:"rail"`, `playbackMode:"hover"` | Pointer hover on desktop and tap interaction on touch devices |
| Visible-card rail | `variant:"rail"`, `playbackMode:"always-visible"` | Every visible card plays muted; offscreen media is released |
| Testimonial coverflow | `variant:"rail"`, `railPresentation:"coverflow"` | Centers and enlarges the active testimonial while neighboring videos overlap and recede |
| Preview grid | `variant:"grid"`, `previewMode:"hover"` | Poster or animated preview tiles open a fullscreen shoppable viewer |

Desktop and mobile do not require separate island instances. Use
`navigationMode` for desktop and `mobileNavigationMode` for mobile.

## Core Props

| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| `items` | array | Yes | - | Video items with media, accessibility text, creator content, and tagged Shopify products |
| `variant` | `"reel" \| "rail" \| "grid"` | No | `"reel"` | Sequence presentation |
| `reelMode` | `"paged" \| "vertical-scroll"` | No | `"paged"` | Paged gesture navigation or native vertical snap scrolling |
| `railPresentation` | `"standard" \| "coverflow"` | No | `"standard"` | Standard horizontal rail or centered overlapping coverflow |
| `aspectRatio` | `"9:16" \| "4:5" \| "1:1"` | No | `"9:16"` | Video-card aspect ratio |
| `viewerMode` | `"inline" \| "fullscreen"` | No | Variant dependent | Inline playback or fullscreen viewer |
| `heading` | string | No | - | Section heading |
| `columns` | `2 \| 3 \| 4` | No | `3` | Responsive grid column target |
| `maxVisibleItems` | number | No | All | Limits the authored sequence |

## Playback And Navigation

| Prop | Type | Default | Description |
|---|---|---|---|
| `playbackMode` | `"manual" \| "active" \| "hover" \| "always-visible"` | `"active"` | Controls which videos play |
| `soundMode` | `"muted" \| "toggle" \| "hover"` | `"muted"` | Sound policy. Browser autoplay begins muted |
| `controlsVisibility` | `"always" \| "hover" \| "never"` | `"hover"` | Play and sound control visibility |
| `navigationMode` | `"buttons" \| "swipe" \| "both" \| "none"` | Variant dependent | Desktop navigation |
| `mobileNavigationMode` | `"buttons" \| "swipe" \| "both" \| "none"` | `"swipe"` | Mobile navigation |
| `autoAdvance` | boolean | `false` | Advances the active card on a timer |
| `autoAdvanceIntervalMs` | number | `4000` | Auto-advance interval |
| `pauseAutoAdvanceOnHover` | boolean | `true` | Pauses timed movement while hovered |
| `pauseAutoAdvanceOnFocus` | boolean | `true` | Pauses timed movement during keyboard interaction |
| `resumeAutoAdvanceAfterMs` | number | `3000` | Delay before timed movement resumes |
| `loop` | boolean | `true` | Loops active videos |
| `showProgress` | boolean | `true` | Shows playback progress |

## Commerce Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `actionMode` | `"add_to_cart" \| "buy_now" \| "view_product"` | `"add_to_cart"` | Product action |
| `productPresentation` | `"hidden" \| "button" \| "chip" \| "card"` | Variant dependent | Hides commerce UI or selects its visible treatment |
| `openCartOnAdd` | boolean | `true` | Opens the configured cart after adding |
| `showProductImage` | boolean | `true` | Shows product imagery |
| `showProductPrice` | boolean | `true` | Shows product pricing |
| `showCreator` | boolean | `true` | Shows creator identity |
| `showCaption` | boolean | `true` | Shows video copy |
| `showMetrics` | boolean | `false` | Shows merchant-supplied social metrics |
| `showBadge` | boolean | `true` | Shows item badges |
| `showTestimonial` | boolean | `false` | Shows the item's author, product label, rating, and verified state |

Product references must use Shopify GIDs. The renderer batches nested product
resolution before hydration, so the island does not fetch product data itself.

## Media Contract

New pages should use the adaptive `media` descriptor:

```json
{
  "id": "routine-1",
  "media": {
    "provider": "cloudflare_stream",
    "poster": "https://cdn.example.com/routine-poster.jpg",
    "preview": {
      "url": "https://cdn.example.com/routine-preview.webp",
      "type": "image/webp"
    },
    "sources": [
      {
        "url": "https://customer.cloudflarestream.com/video/manifest/video.m3u8",
        "type": "application/x-mpegURL"
      },
      {
        "url": "https://cdn.example.com/routine.mp4",
        "type": "video/mp4"
      }
    ]
  },
  "alt": "Creator demonstrates the morning skincare routine",
  "products": [
    {
      "productId": "gid://shopify/Product/123"
    }
  ]
}
```

Supported sources:

- HLS manifests, using native Safari playback or dynamically loaded `hls.js`
- DASH manifests, using dynamically loaded `dash.js`
- MP4, WebM, and Ogg files
- Ordered multi-source fallback
- GIF, animated WebP, or muted MP4 previews
- WebVTT captions through `media.captionsUrl`

The browser never constructs Cloudflare URLs from an asset ID. Supply explicit
public playback and fallback URLs. Signed playback is not part of this contract.

Legacy `src`, `poster`, and `captionsSrc` remain available for compatibility.

## Authoring Examples

### Vertical Autoplay Reel

```html
<lx-island name="ShoppableVideoFeed" hydrate="visible">
  <script type="application/json">
    {
      "variant": "reel",
      "reelMode": "vertical-scroll",
      "playbackMode": "active",
      "soundMode": "toggle",
      "controlsVisibility": "always",
      "navigationMode": "none",
      "mobileNavigationMode": "none",
      "items": [
        {
          "id": "routine-1",
          "src": "https://cdn.example.com/routine.mp4",
          "poster": "https://cdn.example.com/routine.jpg",
          "alt": "Creator demonstrates the product",
          "products": [
            {
              "productId": "gid://shopify/Product/123"
            }
          ]
        }
      ]
    }
  </script>
</lx-island>
```

The active item autoplays muted to comply with browser policy. The shopper can
use the visible sound control after interacting with the page.

### Auto-Advancing Rail Without Icons

```html
<lx-island name="ShoppableVideoFeed" hydrate="visible">
  <script type="application/json">
    {
      "variant": "rail",
      "playbackMode": "active",
      "soundMode": "muted",
      "controlsVisibility": "never",
      "navigationMode": "none",
      "mobileNavigationMode": "swipe",
      "autoAdvance": true,
      "autoAdvanceIntervalMs": 3500,
      "items": [
        {
          "id": "routine-1",
          "src": "https://cdn.example.com/routine.mp4",
          "poster": "https://cdn.example.com/routine.jpg",
          "alt": "Creator demonstrates the product",
          "products": [
            {
              "productId": "gid://shopify/Product/123"
            }
          ]
        }
      ]
    }
  </script>
</lx-island>
```

### Testimonial Coverflow

```html
<lx-island name="ShoppableVideoFeed" hydrate="visible">
  <script type="application/json">
    {
      "variant": "rail",
      "railPresentation": "coverflow",
      "aspectRatio": "4:5",
      "playbackMode": "active",
      "controlsVisibility": "always",
      "navigationMode": "buttons",
      "mobileNavigationMode": "swipe",
      "navigationPlacement": "overlay",
      "productPresentation": "hidden",
      "showCreator": false,
      "showCaption": false,
      "showTestimonial": true,
      "initialIndex": 2,
      "items": [
        {
          "id": "review-maya",
          "src": "https://cdn.example.com/review-maya.mp4",
          "poster": "https://cdn.example.com/review-maya.jpg",
          "alt": "Maya reviews Renewal Cream",
          "testimonial": {
            "author": "Maya F.",
            "productLabel": "Renewal Cream",
            "rating": 5,
            "verified": true
          },
          "products": []
        }
      ]
    }
  </script>
</lx-island>
```

Coverflow remains one responsive island. Desktop buttons center the selected
card, while mobile uses the same authored items with native horizontal swipe
and scroll snapping.

### Animated Preview Grid

```html
<lx-island name="ShoppableVideoFeed" hydrate="visible">
  <script type="application/json">
    {
      "variant": "grid",
      "previewMode": "hover",
      "viewerMode": "fullscreen",
      "columns": 3,
      "mobileNavigationMode": "swipe",
      "items": [
        {
          "id": "routine-1",
          "media": {
            "poster": "https://cdn.example.com/routine.jpg",
            "preview": {
              "url": "https://cdn.example.com/routine-preview.webp",
              "type": "image/webp"
            },
            "sources": [
              {
                "url": "https://cdn.example.com/routine.mp4",
                "type": "video/mp4"
              }
            ]
          },
          "alt": "Creator demonstrates the product",
          "products": [
            {
              "productId": "gid://shopify/Product/123"
            }
          ]
        }
      ]
    }
  </script>
</lx-island>
```

## Loading And Caching

- Grid tiles load posters or previews without attaching full video sources.
- Reel and rail modes attach the active source only.
- `preloadAdjacent:true` warms adjacent metadata when Save-Data is disabled.
- HLS and DASH engines load dynamically only when their source type is selected.
- Offscreen stream instances are destroyed.
- HTTP caching, ETags, byte ranges, and Cloudflare edge caching remain authoritative.
- Reduced-motion disables automated movement and animated previews.

## Custom Styling

Every meaningful element exposes a stable `data-part`. State is available through
attributes including `data-variant`, `data-reel-mode`, `data-active`,
`data-media-state`, `data-playback-mode`, and `data-viewport`.

Common parts:

| Part | Description |
|---|---|
| `root` | Island root |
| `heading` | Section heading |
| `reel`, `reel-stage` | Paged reel |
| `vertical-reel`, `vertical-reel-item` | Native vertical snap reel |
| `rail-shell`, `rail`, `rail-item` | Horizontal rail |
| `testimonial`, `testimonial-author`, `testimonial-product` | Testimonial metadata |
| `testimonial-rating`, `testimonial-star`, `testimonial-verified` | Rating and verification |
| `grid`, `grid-item` | Preview grid |
| `adaptive-video`, `adaptive-video-element` | Shared media primitive |
| `adaptive-video-controls` | Playback controls |
| `adaptive-video-play`, `adaptive-video-mute` | Control buttons |
| `overlay-content` | Creator, caption, and product overlay |
| `product-card`, `product-action` | Commerce surface |
| `navigation`, `nav-previous`, `nav-next` | Sequence navigation |
| `product-tray` | Multi-product tray |
| `viewer`, `viewer-close` | Fullscreen viewer |

Standard design tokens are inherited first:

- `--lx-accent-color`
- `--lx-accent-color-hover`
- `--lx-text-color`
- `--lx-text-muted`
- `--lx-bg-color`
- `--lx-bg-surface`
- `--lx-border-color`
- `--lx-font-heading`
- `--lx-font-body`

Component tokens cover card width, gaps, radius, video object fit, controls,
scrims, creator copy, badges, product cards, buttons, navigation, progress,
product trays, and the viewer. They use the `--lx-shoppable-*` and
`--lx-video-*` prefixes.

Coverflow adds `--lx-shoppable-coverflow-*` tokens for card width, overlap,
active and neighboring scales, stage padding, inactive brightness, duration,
and easing. Testimonial metadata uses `--lx-shoppable-testimonial-*`.

```css
[data-section-id='SECTION'] [data-part="root"] {
  --lx-shoppable-card-width-mobile: min(82vw, 20rem);
  --lx-shoppable-card-width-desktop: 21rem;
  --lx-shoppable-radius: 0.25rem;
  --lx-video-control-bg: rgba(16, 16, 18, 0.72);
  --lx-shoppable-button-bg: var(--lx-accent-color);
}

[data-section-id='SECTION'] [data-part="vertical-reel"] {
  scroll-snap-type: y mandatory;
}

[data-section-id='SECTION'] [data-part="product-card"] {
  border-color: var(--lx-border-color);
}
```

## Events

| Event | Description |
|---|---|
| `lx:video_play` | First playback with video, source, feed, creator, and product context |
| `lx:video_complete` | Completion with watch-time context |
| `lx:video_source_fallback` | Adaptive source fallback |
| `lx:carousel_slide` | Manual or automatic sequence movement |
| `lx:add_to_cart` | Tagged product added through the shared cart path |

## Related Islands

- [VideoPlayer](/islands/content/video-player) - One non-shoppable video
- [ProductGallery](/islands/commerce/product-gallery) - PDP thumbnail, collage, grid, masonry, and stacked media layouts
- [ProductCarousel](/islands/commerce/product-carousel) - Product-first horizontal browsing
- [DrawerShell](/islands/cart/drawer-shell) - Composable cart opened after add-to-cart
