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.
<l-stories>Options
Rounded stories
The default appearance — circular thumbnails with a label below. Videos open in full screen.
Code
<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
<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
<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
<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
<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
<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
listof triggers; viewer is a native modal<dialog>; progress bar hasrole="progressbar"WCAG4.1.2RGAA7.1- Accessible name
Each thumbnail uses
labelas itsaria-label; viewer announces "Story X of N — label" on changeWCAG4.1.2RGAA11.1- Focus management
Native dialog traps focus; closes restore focus to the originating thumbnail
WCAG2.4.3RGAA10.7- Autoplay policy
Video opens muted by default —
mor the mute toggle requires a user gesture to unmuteWCAG1.4.2- Motion
Respects
prefers-reduced-motionfor transitions and progress fill smoothingWCAG2.3.3
Keyboard interactions
API reference
Importing
import 'luxen-ui/stories';
import 'luxen-ui/story';
import 'luxen-ui/stories-viewer';@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 appearanceAttributerounded(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 asLuxenStory[]
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: … }.