Skip to content

Stories <l-stories> <l-story> <l-stories-viewer>

Instagram-style web stories. A horizontal row of clickable thumbnails opens a fullscreen viewer that plays each video with a segmented progress bar, previous/next navigation, mute toggle, and auto-advance.

HTML tag<l-stories>
Native HTML
Progressive
Custom
Shadow DOM
Custom Element (no Shadow DOM)

Options

Rounded stories

The default appearance — circular thumbnails with a label below. Videos open in full screen.

Code
html
<l-stories for="stories-rounded">
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/dog.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/dog.jpg"
    label="Concept"
    pulse
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/sea_turtle.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/sea_turtle.jpg"
    preview="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_400,q_auto/sea_turtle.mp4"
    label="Origin"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/elephants.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/elephants.jpg"
    label="Crafted"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/kitten_fighting.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/kitten_fighting.jpg"
    label="Made by hand"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/big_buck_bunny.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/big_buck_bunny.jpg"
    label="Behind the scenes"
  ></l-story>
</l-stories>

<l-stories-viewer id="stories-rounded"></l-stories-viewer>

The thumbnail ring picks up --ring-color (any background value — solid, linear-gradient, conic-gradient, image) and shows a --ring-offset gap filled with --ring-offset-color. Pair a thicker --ring-width with the gap to make fresh stories stand out:

Code
html
<l-stories
  for="stories-gradient"
  style="
    --ring-color: linear-gradient(
      45deg,
      #f09433 0%,
      #e6683c 25%,
      #dc2743 50%,
      #cc2366 75%,
      #bc1888 100%
    );
    --ring-width: 3px;
    --ring-offset: 3px;
  "
>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/dog.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/dog.jpg"
    label="Anna"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/sea_turtle.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/sea_turtle.jpg"
    label="Bruno"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/elephants.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/elephants.jpg"
    label="Claire"
  ></l-story>
</l-stories>

<l-stories-viewer id="stories-gradient"></l-stories-viewer>

Squared stories

Set appearance="squared" for square thumbnails with rounded corners. Videos open in full screen.

Code
html
<l-stories
  for="stories-squared"
  appearance="squared"
>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/dog.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_1:1,h_400,q_auto/dog.jpg"
    label="The product"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/sea_turtle.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_1:1,h_400,q_auto/sea_turtle.jpg"
    label="How it's made"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/elephants.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_1:1,h_400,q_auto/elephants.jpg"
    label="About us"
  ></l-story>
</l-stories>

<l-stories-viewer id="stories-squared"></l-stories-viewer>

Portrait stories

Set appearance="portrait" for tall video cards (9:16). Videos open in full screen.

Code
html
<l-stories
  for="stories-portrait"
  appearance="portrait"
>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/dog.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_640,q_auto/dog.jpg"
    label="Look #01"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/sea_turtle.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_640,q_auto/sea_turtle.jpg"
    label="Look #02"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/elephants.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_640,q_auto/elephants.jpg"
    label="Look #03"
  ></l-story>
</l-stories>

<l-stories-viewer id="stories-portrait"></l-stories-viewer>

Chapters

Split a single video into chapters with chapters="0,5,12,20" (start times in seconds, comma-separated; 0 is implicit). The progress bar renders one segment per chapter; tap or advances to the next chapter, then auto-advances to the next story at the last chapter.

Code
html
<l-stories for="stories-chapters">
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/big_buck_bunny.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/big_buck_bunny.jpg"
    label="Reviews"
    chapters="0,4,8"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/elephants.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/elephants.jpg"
    label="How it's made"
  ></l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/kitten_fighting.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/kitten_fighting.jpg"
    label="Concept"
  ></l-story>
</l-stories>

<l-stories-viewer id="stories-chapters"></l-stories-viewer>

Shoppable overlay

Slot a CTA into <l-story slot="cta">. The viewer surfaces it only on the active story.

Code
html
<l-stories for="stories-shop">
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/dog.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/dog.jpg"
    label="Best seller"
  >
    <a
      slot="cta"
      href="#"
      class="l-button"
      data-variant="primary"
    >
      Shop the look — 49€
    </a>
  </l-story>
  <l-story
    src="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_1280,q_auto/sea_turtle.mp4"
    poster="https://res.cloudinary.com/demo/video/upload/c_fill,ar_9:16,h_240,q_auto/sea_turtle.jpg"
    label="New drop"
  >
    <a
      slot="cta"
      href="#"
      class="l-button"
      data-variant="primary"
    >
      Shop the drop — 79€
    </a>
  </l-story>
</l-stories>

<l-stories-viewer id="stories-shop"></l-stories-viewer>

Accessibility

Criteria

Roles

Row is a list of triggers; viewer is a native modal <dialog>; progress bar has role="progressbar"

WCAG4.1.2
RGAA7.1
Accessible name

Each thumbnail uses label as its aria-label; viewer announces "Story X of N — label" on change

WCAG4.1.2
RGAA11.1
Focus management

Native dialog traps focus; closes restore focus to the originating thumbnail

WCAG2.4.3
RGAA10.7
Autoplay policy

Video opens muted by default — m or the mute toggle requires a user gesture to unmute

WCAG1.4.2
Motion

Respects prefers-reduced-motion for transitions and progress fill smoothing

WCAG2.3.3

Keyboard interactions

Enter
Opens the viewer at the focused thumbnail
Arrow Left / Right
Previous / next chapter (crosses into the previous / next story at the chapter boundary)
Space
Toggles play/pause
M
Toggles mute
Escape
Closes the viewer

API reference

Importing

js
import 'luxen-ui/stories';
import 'luxen-ui/story';
import 'luxen-ui/stories-viewer';
css
@import 'luxen-ui/css/stories';
@import 'luxen-ui/css/story';

Attributes & Properties

forAttribute
ID of the linked <l-stories-viewer>. If omitted, a singleton viewer is appended on first click
appearanceAttribute
rounded (default) · squared · portrait · landscape

To customize size, radius, gap, or hide labels, set the CSS custom properties directly via inline style or external CSS — no extra attributes needed.

Methods

open(index?)Method
Open the linked viewer at the given story index
stories()Method
Returns the direct <l-story> children as LuxenStory[]

Events

story-openEvent
Fired when a thumbnail is clicked. Detail: { index, story }
story-closeEvent
Fired when the viewer closes. Detail: { index }

CSS custom properties

These tokens are set on <l-stories> and cascade to every <l-story> child.

--sizeName
Thumbnail size. Per-appearance default
--radiusName
Thumbnail border radius. Per-appearance default
--gapName
Gap between thumbnails. Default 1rem
--ring-colorName
Ring color around fresh thumbnails
--ring-color-seenName
Ring color for [seen] thumbnails
--ring-widthName
Ring width. Default 2px
--label-colorName
Label text color

The play-icon disc inside .l-story-play l-icon is styled inline (white icon, 35 % black background). Override it directly via standard CSS: l-story .l-story-play l-icon { background: …; color: … }.