This rune is part of @refrakt-md/media. Install with npm install @refrakt-md/media and add "@refrakt-md/media" to the plugins array in your refrakt.config.json.
Playlist
Curated track listings for albums, podcasts, audiobooks, and mixes. A heading becomes the playlist title, an image becomes the cover art, a paragraph becomes the description, and a list becomes the track listing. Bold text in each list item is the track name, parenthetical text is the duration, and italic text is the per-track artist.
Basic usage
An album with an artist and track listing.
{% playlist type="album" artist="Pink Floyd" media-position="start" %}

---
# The Dark Side of the Moon
A landmark progressive rock album exploring themes of time, death, and madness.
- **Speak to Me** (1:13)
- **Breathe** (2:43)
- **On the Run** (3:36)
- **Time** (7:05)
- **The Great Gig in the Sky** (4:44)
{% /playlist %}<section data-field="content-section" data-rune="playlist" typeof="MusicPlaylist" data-rune-fields="{"type":"album","media-position":"start","artist":"Pink Floyd"}">
<meta content="Pink Floyd" property="byArtist">
<div data-name="media">
<img src="https://assets.refrakt.md/playlist-dark-side-of-the-moon.png" alt="The Dark Side of the Moon" property="image">
</div>
<h1 id="the-dark-side-of-the-moon" data-name="headline" property="name">The Dark Side of the Moon</h1>
<p data-name="blurb">A landmark progressive rock album exploring themes of time, death, and madness.</p>
<ol data-name="tracks">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Speak to Me</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">1:13</span>
<meta property="duration" content="PT73S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Breathe</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">2:43</span>
<meta property="duration" content="PT163S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">On the Run</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">3:36</span>
<meta property="duration" content="PT216S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Time</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">7:05</span>
<meta property="duration" content="PT425S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">The Great Gig in the Sky</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">4:44</span>
<meta property="duration" content="PT284S">
</li>
</ol>
</section>
The Dark Side of the Moon
A landmark progressive rock album exploring themes of time, death, and madness.
- Speak to MePink Floyd1:13
- BreathePink Floyd2:43
- On the RunPink Floyd3:36
- TimePink Floyd7:05
- The Great Gig in the SkyPink Floyd4:44
<section data-field="content-section" typeof="MusicPlaylist" class="rf-playlist rf-playlist--album" data-type="album" data-media-position="start" data-artist="Pink Floyd" data-elevation="flat" data-rune="playlist" data-density="full">
<div data-name="media" class="rf-playlist__media" data-section="media" data-media="cover" data-guest-fit="clip">
<img src="https://assets.refrakt.md/playlist-dark-side-of-the-moon.png" alt="The Dark Side of the Moon" property="image" />
</div>
<div data-name="content" class="rf-playlist__content">
<div data-name="eyebrow" data-zone="eyebrow" data-zone-layout="bar" class="rf-playlist__eyebrow">
<span class="rf-badge" data-meta-type="category">album</span>
</div>
<header data-name="preamble" class="rf-playlist__preamble" data-section="preamble">
<h1 id="the-dark-side-of-the-moon" data-name="headline" property="name" class="rf-playlist__headline" data-section="title">The Dark Side of the Moon</h1>
<p data-name="blurb" class="rf-playlist__blurb" data-section="description">A landmark progressive rock album exploring themes of time, death, and madness.</p>
</header>
<ol data-name="tracks" class="rf-playlist__tracks" data-sequence="numbered">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Speak to Me</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">1:13</span>
<meta property="duration" content="PT73S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Breathe</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">2:43</span>
<meta property="duration" content="PT163S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">On the Run</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">3:36</span>
<meta property="duration" content="PT216S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Time</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">7:05</span>
<meta property="duration" content="PT425S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">The Great Gig in the Sky</span>
<span data-name="track-artist" property="byArtist">Pink Floyd</span>
<span data-name="track-duration">4:44</span>
<meta property="duration" content="PT284S" />
</li>
</ol>
</div>
<meta content="Pink Floyd" property="byArtist" />
</section>Podcast
Use type="podcast" for episodic content with dates.
{% playlist type="podcast" %}
# Design Systems Weekly
A podcast about building and scaling design systems.
- **Component Libraries at Scale** (45:30) — March 2025
- **Token Architecture** (38:15) — February 2025
- **Accessibility First** (42:00) — January 2025
{% /playlist %}<section data-field="content-section" data-rune="playlist" typeof="MusicPlaylist" data-rune-fields="{"type":"podcast","media-position":"top"}">
<h1 id="design-systems-weekly" data-name="headline" property="name">Design Systems Weekly</h1>
<p data-name="blurb">A podcast about building and scaling design systems.</p>
<ol data-name="tracks">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Component Libraries at Scale</span>
<span data-name="track-duration">45:30</span>
<meta property="duration" content="PT2730S">
<span data-name="track-meta" property="datePublished">March 2025</span>
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Token Architecture</span>
<span data-name="track-duration">38:15</span>
<meta property="duration" content="PT2295S">
<span data-name="track-meta" property="datePublished">February 2025</span>
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Accessibility First</span>
<span data-name="track-duration">42:00</span>
<meta property="duration" content="PT2520S">
<span data-name="track-meta" property="datePublished">January 2025</span>
</li>
</ol>
</section>Design Systems Weekly
A podcast about building and scaling design systems.
- Component Libraries at Scale45:30March 2025
- Token Architecture38:15February 2025
- Accessibility First42:00January 2025
<section data-field="content-section" typeof="MusicPlaylist" class="rf-playlist rf-playlist--podcast" data-type="podcast" data-media-position="top" data-elevation="flat" data-rune="playlist" data-density="full">
<div data-name="content" class="rf-playlist__content">
<div data-name="eyebrow" data-zone="eyebrow" data-zone-layout="bar" class="rf-playlist__eyebrow">
<span class="rf-badge" data-meta-type="category">podcast</span>
</div>
<header data-name="preamble" class="rf-playlist__preamble" data-section="preamble">
<h1 id="design-systems-weekly" data-name="headline" property="name" class="rf-playlist__headline" data-section="title">Design Systems Weekly</h1>
<p data-name="blurb" class="rf-playlist__blurb" data-section="description">A podcast about building and scaling design systems.</p>
</header>
<ol data-name="tracks" class="rf-playlist__tracks" data-sequence="numbered">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Component Libraries at Scale</span>
<span data-name="track-duration">45:30</span>
<meta property="duration" content="PT2730S" />
<span data-name="track-meta" property="datePublished">March 2025</span>
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Token Architecture</span>
<span data-name="track-duration">38:15</span>
<meta property="duration" content="PT2295S" />
<span data-name="track-meta" property="datePublished">February 2025</span>
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Accessibility First</span>
<span data-name="track-duration">42:00</span>
<meta property="duration" content="PT2520S" />
<span data-name="track-meta" property="datePublished">January 2025</span>
</li>
</ol>
</div>
</section>Per-track artists
When tracks have different artists, use italic text for per-track attribution. Omit the artist attribute to avoid a default.
{% playlist type="mix" %}
# Summer Vibes 2025
- **Midnight City** (4:03) *M83*
- **Electric Feel** (3:49) *MGMT*
- **Do I Wanna Know?** (4:32) *Arctic Monkeys*
- **Intro** (4:18) *The xx*
{% /playlist %}<section data-field="content-section" data-rune="playlist" typeof="MusicPlaylist" data-rune-fields="{"type":"mix","media-position":"top"}">
<h1 id="summer-vibes-2025" data-name="headline" property="name">Summer Vibes 2025</h1>
<ol data-name="tracks">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Midnight City</span>
<span data-name="track-artist" property="byArtist">M83</span>
<span data-name="track-duration">4:03</span>
<meta property="duration" content="PT243S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Electric Feel</span>
<span data-name="track-artist" property="byArtist">MGMT</span>
<span data-name="track-duration">3:49</span>
<meta property="duration" content="PT229S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Do I Wanna Know?</span>
<span data-name="track-artist" property="byArtist">Arctic Monkeys</span>
<span data-name="track-duration">4:32</span>
<meta property="duration" content="PT272S">
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Intro</span>
<span data-name="track-artist" property="byArtist">The xx</span>
<span data-name="track-duration">4:18</span>
<meta property="duration" content="PT258S">
</li>
</ol>
</section>Summer Vibes 2025
- Midnight CityM834:03
- Electric FeelMGMT3:49
- Do I Wanna Know?Arctic Monkeys4:32
- IntroThe xx4:18
<section data-field="content-section" typeof="MusicPlaylist" class="rf-playlist rf-playlist--mix" data-type="mix" data-media-position="top" data-elevation="flat" data-rune="playlist" data-density="full">
<div data-name="content" class="rf-playlist__content">
<div data-name="eyebrow" data-zone="eyebrow" data-zone-layout="bar" class="rf-playlist__eyebrow">
<span class="rf-badge" data-meta-type="category">mix</span>
</div>
<header data-name="preamble" class="rf-playlist__preamble" data-section="preamble">
<h1 id="summer-vibes-2025" data-name="headline" property="name" class="rf-playlist__headline" data-section="title">Summer Vibes 2025</h1>
</header>
<ol data-name="tracks" class="rf-playlist__tracks" data-sequence="numbered">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Midnight City</span>
<span data-name="track-artist" property="byArtist">M83</span>
<span data-name="track-duration">4:03</span>
<meta property="duration" content="PT243S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Electric Feel</span>
<span data-name="track-artist" property="byArtist">MGMT</span>
<span data-name="track-duration">3:49</span>
<meta property="duration" content="PT229S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Do I Wanna Know?</span>
<span data-name="track-artist" property="byArtist">Arctic Monkeys</span>
<span data-name="track-duration">4:32</span>
<meta property="duration" content="PT272S" />
</li>
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Intro</span>
<span data-name="track-artist" property="byArtist">The xx</span>
<span data-name="track-duration">4:18</span>
<meta property="duration" content="PT258S" />
</li>
</ol>
</div>
</section>With chapter markers
Tracks can include nested lists for chapter markers or lyrics. Use content="chapters" or content="lyrics" to force the display mode, or leave it as auto for automatic detection.
{% playlist type="podcast" content="chapters" %}
# Tech Talk: Web Components
- **Episode 42: Shadow DOM Deep Dive** (1:02:15)
1. **Introduction** (0:00) Overview of today's topic
2. **What is Shadow DOM?** (3:45) Core concepts explained
3. **Styling strategies** (18:30) CSS custom properties and parts
4. **Q&A** (45:00) Listener questions
{% /playlist %}<section data-field="content-section" data-rune="playlist" typeof="MusicPlaylist" data-rune-fields="{"type":"podcast","media-position":"top"}">
<h1 id="tech-talk-web-components" data-name="headline" property="name">Tech Talk: Web Components</h1>
<ol data-name="tracks">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Episode 42: Shadow DOM Deep Dive</span>
<span data-name="track-duration">1:02:15</span>
<meta property="duration" content="PT3735S">
<ol data-name="chapters">
<li data-time="0">
<span data-name="chapter-name">Introduction</span>
<span data-name="chapter-time">0:00</span>
</li>
<li data-time="225">
<span data-name="chapter-name">What is Shadow DOM?</span>
<span data-name="chapter-time">3:45</span>
</li>
<li data-time="1110">
<span data-name="chapter-name">Styling strategies</span>
<span data-name="chapter-time">18:30</span>
</li>
<li data-time="2700">
<span data-name="chapter-name">Q&A</span>
<span data-name="chapter-time">45:00</span>
</li>
</ol>
</li>
</ol>
</section>Tech Talk: Web Components
- Episode 42: Shadow DOM Deep Dive1:02:15
- Introduction0:00
- What is Shadow DOM?3:45
- Styling strategies18:30
- Q&A45:00
<section data-field="content-section" typeof="MusicPlaylist" class="rf-playlist rf-playlist--podcast" data-type="podcast" data-media-position="top" data-elevation="flat" data-rune="playlist" data-density="full">
<div data-name="content" class="rf-playlist__content">
<div data-name="eyebrow" data-zone="eyebrow" data-zone-layout="bar" class="rf-playlist__eyebrow">
<span class="rf-badge" data-meta-type="category">podcast</span>
</div>
<header data-name="preamble" class="rf-playlist__preamble" data-section="preamble">
<h1 id="tech-talk-web-components" data-name="headline" property="name" class="rf-playlist__headline" data-section="title">Tech Talk: Web Components</h1>
</header>
<ol data-name="tracks" class="rf-playlist__tracks" data-sequence="numbered">
<li typeof="MusicRecording" data-field="track" property="track">
<span data-name="track-name" property="name">Episode 42: Shadow DOM Deep Dive</span>
<span data-name="track-duration">1:02:15</span>
<meta property="duration" content="PT3735S" />
<ol data-name="chapters">
<li data-time="0">
<span data-name="chapter-name">Introduction</span>
<span data-name="chapter-time">0:00</span>
</li>
<li data-time="225">
<span data-name="chapter-name">What is Shadow DOM?</span>
<span data-name="chapter-time">3:45</span>
</li>
<li data-time="1110">
<span data-name="chapter-name">Styling strategies</span>
<span data-name="chapter-time">18:30</span>
</li>
<li data-time="2700">
<span data-name="chapter-name">Q&A</span>
<span data-name="chapter-time">45:00</span>
</li>
</ol>
</li>
</ol>
</div>
</section>Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
type | "album" | "podcast" | "audiobook" | "series" | "mix" | — | Collection format: album, podcast, audiobook, series, or mix. |
artist | string | — | Default artist applied to tracks that omit their own. |
player | boolean | — | Enable/disable an embedded audio player for the playlist. |
content | "auto" | "lyrics" | "chapters" | — | How nested lists are interpreted: auto-detect, lyrics, or chapters. |
id | string | — | Unique identifier used to link an audio rune to this playlist. |
Inherited from the split layout preset
| Attribute | Type | Required | Description |
|---|---|---|---|
media-position | "top" | "bottom" | "start" | "end" | "cover" | — | Where the media zone sits relative to the content: above (top), below (bottom), or beside (start/end) |
media-ratio | "1/3" | "2/5" | "1/2" | "3/5" | "2/3" | — | Media zone’s share of the row width when media is beside content (start/end) |
valign | "top" | "center" | "bottom" | "stretch" | — | Cross-axis alignment when media is beside content (start/end); applies to the shorter zone |
collapse | "sm" | "md" | "lg" | "never" | — | Breakpoint at which side-by-side layouts collapse to a single stacked column |
Universal attributes
bg
The background layer (SPEC-088): image, video, gradient, flat overlay wash and legibility scrim, in a single injected layer behind the rune's content.
| Attribute | Type | Required | Description |
|---|---|---|---|
bg | string | — | Background preset applied to this block |
bg-from | string | — | Gradient start colour — a semantic token name (→ var(--rf-color-*)) |
bg-gradient | "to-t" | "to-b" | "to-l" | "to-r" | "to-tr" | "to-br" | "to-bl" | "to-tl" | — | Gradient direction (bounded named set) |
bg-gradient-type | "linear" | "radial" | "conic" | — | Gradient type |
bg-to | string | — | Gradient end colour — a semantic token name |
bg-via | string | — | Optional middle gradient stop — a semantic token name |
scrim | "top" | "bottom" | "left" | "right" | "none" | — | Scrim direction (heaviest edge); presence turns the scrim on, "none" opts out of the default cover scrim |
scrim-blur | "none" | "sm" | "md" | "lg" | — | Frost scrim blur amount |
scrim-strength | "sm" | "md" | "lg" | — | Gradient scrim strength |
scrim-tone | "dark" | "light" | — | Whether the scrim darkens (for light text) or lightens (for dark text) |
scrim-type | "gradient" | "frost" | — | Scrim treatment: gradient (default) or frost (backdrop blur) |
dropcap
Per-instance drop-cap opt-in (SPEC-108).
| Attribute | Type | Required | Description |
|---|---|---|---|
dropcap | boolean | — | Style the opening letter of a prose body as a drop cap (SPEC-108). Honoured only when the body reads as prose; ignored otherwise. |
elevation
The chrome/depth ladder (SPEC-107). The skin maps each rung to a chrome bundle by attribute, so there is no BEM class.
| Attribute | Type | Required | Description |
|---|---|---|---|
elevation | "sunken" | "flush" | "flat" | "raised" | "floating" | "overlay" | "none" | "sm" | "md" | "lg" | — | Surface depth on the SPEC-107 ladder (sunken→overlay); none/sm/md/lg are deprecated aliases |
frame
Surface chrome (SPEC-086): a named preset from the theme registry with inline `frame-*` overrides layered on top.
| Attribute | Type | Required | Description |
|---|---|---|---|
frame | string | — | Named frame preset presenting this block's media surface |
frame-anchor | string | — | Crop focal point when the guest is cut (object-position) |
frame-aspect | string | — | Aspect ratio of the framed media, e.g. "16/9" |
frame-displace | "top" | "bottom" | "end" | "bottom-end" | "top-end" | — | Edge/corner the framed guest moves toward |
frame-displace-mode | "peek" | "bleed" | — | How displacement renders: `peek` (default) translates the guest visually inside its frame target; `bleed` uses negative margin on the media zone so following layout pulls up — extends past a section like a hero without leaving a gap above |
frame-offset | "none" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | — | Displacement distance (non-linear named scale: sm…xl ride the block-spacing tokens, 2xl/3xl/4xl step up to section-spacing for bleed-mode displacements that clear a section edge) |
frame-overflow | "clip" | "bleed" | — | How the frame handles a guest whose content is wider than it: `clip` (default) keeps it inside the rounded frame; `bleed` runs an overflowing guest out to the layout edge on a narrow viewport so it reads as cropped by the screen (a bleed host only — hero/feature) |
frame-oversize | string | — | How far the guest exceeds its slot (scale factor) |
frame-place | string | — | Guest-box alignment in the slot (e.g. "left top") |
frame-shadow | "none" | "sm" | "md" | "lg" | — | Silhouette drop-shadow strength for the framed media |
inset
Internal padding override.
| Attribute | Type | Required | Description |
|---|---|---|---|
inset | "flush" | "tight" | "default" | "loose" | "breathe" | — | Inner padding of this block |
motion
Scroll-reveal entrance (SPEC-105). The author declares the character, the theme owns the choreography, a behaviour owns the timing.
| Attribute | Type | Required | Description |
|---|---|---|---|
reveal | "none" | "fade" | "slide" | "scale" | "blur" | — | Scroll-reveal entrance character (none|fade|slide|scale|blur); the theme owns the choreography |
stagger | boolean | — | Cascade this block's items in as it reveals (no-op on single-child runes) |
prominence
Header emphasis (SPEC-107). Scales a rune's page-section header; the skin maps it to a type register by attribute, so there is no BEM class.
| Attribute | Type | Required | Description |
|---|---|---|---|
prominence | "quiet" | "normal" | "prominent" | "display" | — | Section-header emphasis (only on page-section-header family runes) |
reading
Editorial register for body text (SPEC-108). The author picks the register; the theme owns the magnitude.
| Attribute | Type | Required | Description |
|---|---|---|---|
reading | "fine" | "ui" | "prose" | — | Reading register for this block’s body (SPEC-108): fine | ui | prose. The theme owns the editorial treatment. |
spacing
Block-level rhythm override.
| Attribute | Type | Required | Description |
|---|---|---|---|
spacing | "flush" | "tight" | "default" | "loose" | "breathe" | — | Vertical spacing above and below this block |
substrate
Generated pattern fills (SPEC-087). Markers only — the engine sets the attributes and cell/opacity custom properties, CSS draws the pattern.
| Attribute | Type | Required | Description |
|---|---|---|---|
substrate | "dots" | "grid" | "lines" | "cross" | "checker" | "none" | — | Generated surface pattern |
substrate-fill | "inherit" | "inset" | — | Surface fill the pattern sits on (full colour stays with tint) |
substrate-opacity | "sm" | "md" | "lg" | — | Pattern ink strength |
substrate-size | "sm" | "md" | "lg" | — | Pattern cell size |
substrate-target | "self" | "media" | — | Which surface the pattern fills (overrides the rune/theme default) |
tint
Per-rune colour override (SPEC-053): a named tint from the theme registry, with inline per-token overrides layered on top.
| Attribute | Type | Required | Description |
|---|---|---|---|
tint | string | — | Color tint preset applied to this block |
tint-mode | "auto" | "dark" | "light" | — | Whether the tint adapts to auto, dark, or light mode |
width
The track a block rune occupies.
| Attribute | Type | Required | Description |
|---|---|---|---|
width | "compact" | "narrow" | "content" | "wide" | "full" | — | Maximum width constraint for this block |
Section header
Playlist supports an optional eyebrow, headline, and blurb above the playlist content. Place a short paragraph or heading before the main content to use them. See Page sections for the full syntax.
Layout attributes
The body splits on --- into media (cover art) → content (tracks) zones (media-first in source). media-position controls visual placement independently of source order.
| Attribute | Type | Default | Description |
|---|---|---|---|
media-position | string | top | Where the cover sits: top, bottom, start (left), end (right) |
media-ratio | string | — | Cover's share of the row when beside content (start/end): 1/3, 2/5, 1/2, 3/5, 2/3 |
valign | string | — | Cross-axis alignment when cover is beside content: top, center, bottom, stretch |
collapse | string | — | Breakpoint at which beside layouts collapse to a stack: sm, md, lg, never |