---
outline: deep
---

# Stories viewer

The fullscreen modal that plays a sequence of [`<l-story>`](/elements/story) videos. Linked to one or more [`<l-stories>`](/elements/stories) rows via matching `id` ↔ `for`. See [Stories](/elements/stories) for the full composition pattern, examples, and accessibility notes.

**`<l-stories-viewer>`** — Custom Element · Shadow DOM

## API reference

### Importing

```js
import 'luxen-ui/stories-viewer';
```

### Attributes & Properties

- **open**: `boolean` (default: `false`) — Whether the viewer is open.
- **index**: `number` (default: `0`) — Active story index (0-based).
- **muted**: `boolean` (default: `true`) — Whether playback is muted. so autoplay always succeeds across browsers. The user can unmute via the dedicated button or the `m` keyboard shortcut.
- **loop**: `boolean` (default: `false`) — Loop the active story instead of advancing.
- **auto-advance**: `boolean` (default: `true`) — Move to the next story when the current one ends; close after the last story.
- **light-dismiss**: `boolean` (default: `true`) — Close when the backdrop is clicked.
- **chapter**: `number` (default: `0`) — Active chapter index within the current story. Reflected.
- **stories**: `LuxenStory[]` (default: `[]`) — Internal: the playlist set by the source `<l-stories>`.
- **source**: `LuxenStories | null` (default: `null`) — Internal: the source `<l-stories>` element that opened the viewer.

### Methods

- **openAt(stories: LuxenStory[], index: unknown, source: LuxenStories | null)** — Open the viewer at the given index with an explicit playlist.
- **close()**
- **next()** — Advance one chapter, or to the next story at the chapter boundary.
- **previous()** — Retreat one chapter, restart the current chapter past 1s in, or cross into the previous story.
- **nextStory()** — Jump to the next story, skipping any remaining chapters in the current story.
- **previousStory()** — Jump to the previous story regardless of current chapter. Lands on chapter 0 of the previous story.
- **play()**
- **pause()**

### Events

- **show** (cancelable) — Fired when the viewer is about to open. Cancelable — call `event.preventDefault()` to keep it closed.
- **after-show** — Fired after the open transition completes. Not cancelable.
- **hide** (cancelable) — Fired when the viewer is about to close. Cancelable.
- **after-hide** — Fired after the close transition completes. Not cancelable.
- **story-change** — Fired when the active story changes. Properties: `index: number`, `story: LuxenStory`.
- **story-end** — Fired when the active story finishes playback. Properties: `index: number`.
- **chapter-change** — Fired when the active chapter (within a story) changes. Properties: `chapter: number`, `story: LuxenStory`.
- **mute-change** — Fired when the mute state changes. Properties: `muted: boolean`.

### Slots

- **cta** — Default CTA overlay (e.g. shoppable card). Per-story `slot="cta"` inside `<l-story>` overrides this when that story is active.
- **header** — Default header overlay (e.g. avatar + author). Per-story override available the same way.
- **close** — Override the default close button.

### CSS parts

- `dialog` — The native `<dialog>` element.
- `frame` — The aspect-ratio video frame.
- `progress` — The progress bar wrapper.
- `progress-segment` — A single progress segment.
- `progress-fill` — The fill element inside an active segment.
- `video` — The `<video>` element.
- `overlay` — The overlay wrapper that hosts CTA/header slots.
- `header` — The top-left header area (story thumbnail + label fallback, or consumer-supplied content via the `header` slot).
- `header-label` — The default story label inside the header.
- `actions` — The top-right vertical button stack (close, play/pause, mute).
- `button-close` — The close button.
- `button-pause` — The play/pause toggle.
- `button-mute` — The mute toggle.
- `button-previous` — The previous story button.
- `button-next` — The next story button.
- `spinner` — The loading spinner shown while the current video is buffering.

### CSS custom properties

- `--width` (default: `min(420px, 100vw)`) — Frame width.
- `--progress-color` (default: `white`) — Active progress fill color.
- `--progress-bg` (default: `rgb(255 255 255 / 35%)`) — Inactive progress segment background.
- `--progress-gap` (default: `4px`) — Gap between segments.
- `--show-duration` (default: `200ms`) — Open transition duration.
- `--hide-duration` (default: `200ms`) — Close transition duration.
- `--backdrop` (default: `var(--l-backdrop-strong)`) — Backdrop color. — darker than `--l-backdrop` to focus attention on the immersive frame, but still translucent so the page stays perceptible.

To change the frame ratio (e.g. `4/5` or `1/1`), target the `frame` part:

```css
l-stories-viewer::part(frame) {
  aspect-ratio: 4/5;
}
```

The `<video>` defaults to `object-fit: cover` so portrait videos fill the 9/16 frame edge-to-edge. For mixed catalogs with landscape or square videos, switch to `contain` to letterbox instead of cropping:

```css
l-stories-viewer::part(video) {
  object-fit: contain;
}
```
