Open demo

CSS code

HTML
<div><span class="lbl">Album with track list</span>
<section class="pl" data-pl aria-label="Low Tide Radio playlist">
  <audio src="../audio/track-1.mp3" preload="metadata"></audio>
  <div class="pl-head">
    <div class="pl-art" data-cover style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)" aria-hidden="true"></div>
    <div class="pl-info">
      <div><p class="pl-eyebrow">Playlist · Low Tide Radio</p><p class="pl-now" data-show="name">Harbour Lights</p><p class="pl-by" data-show="artist">Juniper Vale</p></div>
      <div class="pl-ctrls">
        <button type="button" class="pl-skip" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
        <button type="button" class="pl-play" data-play aria-label="Play Harbour Lights"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
        <button type="button" class="pl-skip" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
      </div>
      <div class="pl-bar">
        <span class="pl-time" data-cur>0:00</span>
        <input type="range" data-seek min="0" max="24" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:24">
        <span class="pl-time" data-dur>0:24</span>
      </div>
    </div>
  </div>
  <ol class="pl-list">
    <li><button type="button" class="pl-item" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale" aria-current="true">
      <span class="pl-num"><span class="pl-n">1</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)"></span>
      <span><span class="pl-name">Harbour Lights</span><span class="pl-artist">Juniper Vale</span></span>
      <span class="pl-len">0:24</span></button></li>
    <li><button type="button" class="pl-item" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir">
      <span class="pl-num"><span class="pl-n">2</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)"></span>
      <span><span class="pl-name">Paper Kites</span><span class="pl-artist">The Lumen Choir</span></span>
      <span class="pl-len">0:24</span></button></li>
    <li><button type="button" class="pl-item" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch">
      <span class="pl-num"><span class="pl-n">3</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)"></span>
      <span><span class="pl-name">Night Bus to Nowhere</span><span class="pl-artist">Orla Finch</span></span>
      <span class="pl-len">0:30</span></button></li>
    <li><button type="button" class="pl-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="pl-num"><span class="pl-n">4</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span>
      <span><span class="pl-name">Copper Sky</span><span class="pl-artist">Milo & the Tidelines</span></span>
      <span class="pl-len">0:29</span></button></li>
  </ol>
</section></div>

<div><span class="lbl">Side-by-side queue</span>
<section class="pl pl-side" data-pl aria-label="Late night queue">
  <audio src="../audio/track-3.mp3" preload="metadata"></audio>
  <div class="pl-main">
    <div class="pl-art" data-cover style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)" aria-hidden="true"></div>
    <div><p class="pl-now" data-show="name">Night Bus to Nowhere</p><p class="pl-by" data-show="artist">Orla Finch</p></div>
    <div class="pl-bar">
      <span class="pl-time" data-cur>0:00</span>
      <input type="range" data-seek min="0" max="30" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:30">
      <span class="pl-time" data-dur>0:30</span>
    </div>
    <div class="pl-ctrls">
      <button type="button" class="pl-skip" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
      <button type="button" class="pl-play" data-play aria-label="Play Night Bus to Nowhere"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
      <button type="button" class="pl-skip" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
    </div>
  </div>
  <div class="pl-queue">
    <p class="pl-qh">Queue</p>
    <ol class="pl-list">
      <li><button type="button" class="pl-item" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch" aria-current="true">
        <span class="pl-num"><span class="pl-n">1</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
        <span class="pl-art" data-art style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)"></span>
        <span><span class="pl-name">Night Bus to Nowhere</span><span class="pl-artist">Orla Finch</span></span>
        <span class="pl-len">0:30</span></button></li>
      <li><button type="button" class="pl-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
        <span class="pl-num"><span class="pl-n">2</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
        <span class="pl-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span>
        <span><span class="pl-name">Copper Sky</span><span class="pl-artist">Milo & the Tidelines</span></span>
        <span class="pl-len">0:29</span></button></li>
      <li><button type="button" class="pl-item" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir">
        <span class="pl-num"><span class="pl-n">3</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
        <span class="pl-art" data-art style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)"></span>
        <span><span class="pl-name">Paper Kites</span><span class="pl-artist">The Lumen Choir</span></span>
        <span class="pl-len">0:24</span></button></li>
    </ol>
  </div>
</section></div>
CSS
/* Playlist players: one <audio>, a header with the current track and a list of track buttons.
   The script sets --p (0 to 1) on .pl, toggles [data-playing] and moves aria-current between tracks. */
.pl{--pl-accent:#047857;--pl-soft:#ecfdf5;--pl-ink:#0f172a;--pl-muted:#475569;--pl-track:#8391a7;--pl-thumb:14px;--pl-h:4px;--p:0;
  color:var(--pl-ink);background:#fff;border:1px solid #e2e8f0;border-radius:22px;box-shadow:0 10px 30px rgba(15,23,42,.07);overflow:hidden}
.pl button{display:grid;place-items:center;border:0;background:none;color:inherit;cursor:pointer;font:inherit}
.pl button:focus-visible{outline:2px solid var(--pl-accent);outline-offset:2px}
.pl svg{display:block;width:20px;height:20px}
.pl-art{border-radius:14px;background:var(--art)}
.pl-eyebrow{font-size:.7rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--pl-accent)}
.pl-now{font-size:1.3rem;font-weight:800;letter-spacing:-.02em;margin-top:4px}
.pl-by{font-size:.9rem;color:var(--pl-muted);margin-top:2px}
.pl-ctrls{display:flex;align-items:center;gap:8px}
.pl .pl-skip{width:40px;height:40px;border-radius:50%;color:var(--pl-ink)}
.pl .pl-skip:hover{background:#f1f5f9}
.pl .pl-play{width:48px;height:48px;border-radius:50%;background:var(--pl-accent);color:#fff;box-shadow:0 6px 16px rgba(4,120,87,.3)}
.pl .pl-play svg{width:22px;height:22px}
.pl .i-pause,.pl[data-playing] .i-play{display:none}
.pl[data-playing] .i-pause{display:block}
.pl-bar{display:flex;align-items:center;gap:8px}
.pl-time{font-size:.78rem;font-variant-numeric:tabular-nums;color:var(--pl-muted);min-width:2.3em}
.pl-bar .pl-time:last-child{text-align:right}
.pl input[type="range"]{-webkit-appearance:none;appearance:none;display:block;flex:1;min-width:0;height:var(--pl-thumb);margin:0;background:none;cursor:pointer}
.pl input[type="range"]:focus-visible{outline:none}
.pl input[type="range"]::-webkit-slider-runnable-track{height:var(--pl-h);border-radius:999px;
  background:linear-gradient(to right,var(--pl-accent) 0 calc(var(--pl-thumb) / 2 + (100% - var(--pl-thumb)) * var(--p)),var(--pl-track) 0)}
.pl input[type="range"]::-moz-range-track{height:var(--pl-h);border-radius:999px;
  background:linear-gradient(to right,var(--pl-accent) 0 calc(var(--pl-thumb) / 2 + (100% - var(--pl-thumb)) * var(--p)),var(--pl-track) 0)}
.pl input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:var(--pl-thumb);height:var(--pl-thumb);
  margin-top:calc((var(--pl-h) - var(--pl-thumb)) / 2);border:3px solid var(--pl-accent);border-radius:50%;background:#fff;box-sizing:border-box}
.pl input[type="range"]::-moz-range-thumb{width:var(--pl-thumb);height:var(--pl-thumb);border:3px solid var(--pl-accent);border-radius:50%;background:#fff;box-sizing:border-box}
.pl input[type="range"]:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--pl-accent)}
.pl input[type="range"]:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--pl-accent)}
/* track list */
.pl-list{list-style:none;padding:8px}
.pl .pl-item{width:100%;display:grid;place-items:center stretch;grid-template-columns:28px 40px 1fr auto;align-items:center;gap:12px;padding:8px 12px;border-radius:12px;text-align:left}
.pl .pl-item:hover{background:#f8fafc}
.pl .pl-item[aria-current="true"]{background:var(--pl-soft)}
.pl .pl-item:focus-visible{outline-offset:-2px}
.pl-num{font-size:.85rem;color:var(--pl-muted);font-variant-numeric:tabular-nums;text-align:center}
.pl-item .pl-art{width:40px;height:40px;border-radius:8px}
.pl-name{display:block;font-weight:600;font-size:.95rem}
.pl-artist{display:block;font-size:.8rem;color:var(--pl-muted);margin-top:2px}
.pl-len{font-size:.8rem;color:var(--pl-muted);font-variant-numeric:tabular-nums}
.pl-item[aria-current="true"] .pl-name{color:#065f46}
/* the current row swaps its number for three small bars that move while playing */
.pl-eq{display:none;height:14px;align-items:flex-end;justify-content:center;gap:2px}
.pl-eq i{width:3px;height:100%;border-radius:1px;background:var(--pl-accent);transform-origin:bottom}
.pl-eq i:nth-child(2){height:60%}.pl-eq i:nth-child(3){height:80%}
.pl-item[aria-current="true"] .pl-n{display:none}
.pl-item[aria-current="true"] .pl-eq{display:flex}
@keyframes pl-bounce{50%{transform:scaleY(.35)}}
@media (prefers-reduced-motion:no-preference){
  .pl[data-playing] .pl-eq i{animation:pl-bounce .9s ease-in-out infinite}
  .pl[data-playing] .pl-eq i:nth-child(2){animation-duration:.7s}
  .pl[data-playing] .pl-eq i:nth-child(3){animation-duration:1.1s}
}
/* 1. album with track list */
.pl-head{display:grid;grid-template-columns:150px 1fr;gap:22px;padding:22px;border-bottom:1px solid #e2e8f0;align-items:center}
.pl-head .pl-art{aspect-ratio:1}
.pl-info{display:flex;flex-direction:column;gap:16px;min-width:0}
/* 2. side-by-side queue */
.pl-side{display:grid;grid-template-columns:280px 1fr}
.pl-main{display:flex;flex-direction:column;gap:16px;padding:22px;border-right:1px solid #e2e8f0}
.pl-main .pl-art{aspect-ratio:1}
.pl-main .pl-ctrls{justify-content:center}
.pl-queue{padding:14px 6px}
.pl-qh{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:var(--pl-muted);padding:8px 16px 0}
@media (max-width:560px){
  .pl-head{grid-template-columns:96px 1fr;gap:16px;padding:16px}
  .pl-side{grid-template-columns:1fr}
  .pl-main{border-right:0;border-bottom:1px solid #e2e8f0}
  .pl-main .pl-art{aspect-ratio:2 / 1}
  .pl .pl-item{grid-template-columns:22px 36px 1fr auto;gap:10px;padding:8px}
  .pl-item .pl-art{width:36px;height:36px}
}
JavaScript
// Playlist players. Each [data-pl] holds one <audio> and a list of track buttons that carry
// data-src, data-name and data-artist. The current track button gets aria-current="true".
(() => {
  const fmt = s => { s = Math.floor(s || 0); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
  document.querySelectorAll('[data-pl]').forEach(root => {
    const $ = s => root.querySelector(s);
    const audio = $('audio'), play = $('[data-play]'), seek = $('[data-seek]'), cur = $('[data-cur]'), dur = $('[data-dur]');
    const cover = $('[data-cover]'), items = [...root.querySelectorAll('[data-src]')];
    let i = Math.max(0, items.findIndex(b => b.getAttribute('aria-current') === 'true')), raf = 0;
    const label = () => play.setAttribute('aria-label', (audio.paused ? 'Play ' : 'Pause ') + items[i].dataset.name);
    const length = () => audio.duration || +seek.max;
    const sync = () => {
      const t = audio.currentTime, d = length(), text = fmt(t) + ' of ' + fmt(d);
      seek.value = t;
      root.style.setProperty('--p', d ? t / d : 0);
      cur.textContent = fmt(t);
      if (seek.getAttribute('aria-valuetext') !== text) seek.setAttribute('aria-valuetext', text);
    };
    const tick = () => { sync(); raf = requestAnimationFrame(tick); };
    const meta = () => { seek.max = audio.duration; dur.textContent = fmt(audio.duration); sync(); };
    const start = () => {
      document.querySelectorAll('audio').forEach(a => { if (a !== audio) a.pause(); });
      audio.play().catch(() => {});
    };
    // Switch to track n: mark it current, swap the source, title, artist and cover art.
    const load = n => {
      i = (n + items.length) % items.length;
      const item = items[i], art = item.querySelector('[data-art]');
      items.forEach(b => b.removeAttribute('aria-current'));
      item.setAttribute('aria-current', 'true');
      audio.src = item.dataset.src;
      root.querySelectorAll('[data-show]').forEach(el => { el.textContent = item.dataset[el.dataset.show]; });
      if (cover && art) {
        const cs = getComputedStyle(art);
        ['backgroundImage', 'backgroundPosition', 'backgroundSize', 'backgroundRepeat'].forEach(p => { cover.style[p] = cs[p]; });
      }
      label();
    };
    play.addEventListener('click', () => (audio.paused ? start() : audio.pause()));
    $('[data-prev]').addEventListener('click', () => {
      if (audio.currentTime > 3) audio.currentTime = 0; else load(i - 1);
      start();
    });
    $('[data-next]').addEventListener('click', () => { load(i + 1); start(); });
    items.forEach((item, n) => item.addEventListener('click', () => {
      if (n === i) return play.click();
      load(n); start();
    }));
    audio.addEventListener('play', () => { root.setAttribute('data-playing', ''); label(); tick(); });
    audio.addEventListener('pause', () => { root.removeAttribute('data-playing'); label(); cancelAnimationFrame(raf); sync(); });
    audio.addEventListener('ended', () => { if (i < items.length - 1) { load(i + 1); start(); } });
    audio.addEventListener('loadedmetadata', meta);
    if (audio.readyState > 0) meta();
    seek.addEventListener('input', () => { audio.currentTime = seek.value; sync(); });
    // Arrow keys jump 5 seconds; Home and End work natively.
    seek.addEventListener('keydown', e => {
      const step = { ArrowLeft: -5, ArrowDown: -5, ArrowRight: 5, ArrowUp: 5 }[e.key];
      if (!step) return;
      e.preventDefault();
      audio.currentTime = Math.min(Math.max(audio.currentTime + step, 0), length());
      sync();
    });
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Album with track list</span>
<section class="card pl" data-pl aria-label="Low Tide Radio playlist">
  <audio src="../audio/track-1.mp3" preload="metadata"></audio>
  <div class="pl-head">
    <div class="pl-art" data-cover style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)" aria-hidden="true"></div>
    <div class="d-flex flex-column gap-3" style="min-width:0">
      <div><p class="pl-eyebrow mb-0">Playlist · Low Tide Radio</p><p class="pl-now mb-0" data-show="name">Harbour Lights</p><p class="pl-by mb-0" data-show="artist">Juniper Vale</p></div>
      <div class="d-flex align-items-center gap-2">
        <button type="button" class="btn pl-skip rounded-circle" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
        <button type="button" class="btn pl-play rounded-circle" data-play aria-label="Play Harbour Lights"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
        <button type="button" class="btn pl-skip rounded-circle" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
      </div>
      <div class="d-flex align-items-center gap-2">
        <span class="pl-time" data-cur>0:00</span>
        <input type="range" class="form-range" data-seek min="0" max="24" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:24">
        <span class="pl-time text-end" data-dur>0:24</span>
      </div>
    </div>
  </div>
  <ol class="list-group list-group-flush">
    <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale" aria-current="true">
      <span class="pl-num"><span class="pl-n">1</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)"></span>
      <span><span class="pl-name">Harbour Lights</span><span class="pl-artist">Juniper Vale</span></span>
      <span class="pl-len">0:24</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir">
      <span class="pl-num"><span class="pl-n">2</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)"></span>
      <span><span class="pl-name">Paper Kites</span><span class="pl-artist">The Lumen Choir</span></span>
      <span class="pl-len">0:24</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch">
      <span class="pl-num"><span class="pl-n">3</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)"></span>
      <span><span class="pl-name">Night Bus to Nowhere</span><span class="pl-artist">Orla Finch</span></span>
      <span class="pl-len">0:30</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="pl-num"><span class="pl-n">4</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
      <span class="pl-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span>
      <span><span class="pl-name">Copper Sky</span><span class="pl-artist">Milo & the Tidelines</span></span>
      <span class="pl-len">0:29</span></button></li>
  </ol>
</section></div>

<div><span class="lbl">Side-by-side queue</span>
<section class="card pl pl-side" data-pl aria-label="Late night queue">
  <audio src="../audio/track-3.mp3" preload="metadata"></audio>
  <div class="pl-main d-flex flex-column gap-3">
    <div class="pl-art" data-cover style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)" aria-hidden="true"></div>
    <div><p class="pl-now mb-0" data-show="name">Night Bus to Nowhere</p><p class="pl-by mb-0" data-show="artist">Orla Finch</p></div>
    <div class="d-flex align-items-center gap-2">
      <span class="pl-time" data-cur>0:00</span>
      <input type="range" class="form-range" data-seek min="0" max="30" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:30">
      <span class="pl-time text-end" data-dur>0:30</span>
    </div>
    <div class="d-flex align-items-center justify-content-center gap-2">
      <button type="button" class="btn pl-skip rounded-circle" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
      <button type="button" class="btn pl-play rounded-circle" data-play aria-label="Play Night Bus to Nowhere"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
      <button type="button" class="btn pl-skip rounded-circle" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
    </div>
  </div>
  <div class="pl-queue">
    <p class="pl-qh mb-0">Queue</p>
    <ol class="list-group list-group-flush">
      <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch" aria-current="true">
        <span class="pl-num"><span class="pl-n">1</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
        <span class="pl-art" data-art style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)"></span>
        <span><span class="pl-name">Night Bus to Nowhere</span><span class="pl-artist">Orla Finch</span></span>
        <span class="pl-len">0:30</span></button></li>
      <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
        <span class="pl-num"><span class="pl-n">2</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
        <span class="pl-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span>
        <span><span class="pl-name">Copper Sky</span><span class="pl-artist">Milo & the Tidelines</span></span>
        <span class="pl-len">0:29</span></button></li>
      <li class="list-group-item"><button type="button" class="list-group-item-action pl-item" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir">
        <span class="pl-num"><span class="pl-n">3</span><span class="pl-eq" aria-hidden="true"><i></i><i></i><i></i></span></span>
        <span class="pl-art" data-art style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)"></span>
        <span><span class="pl-name">Paper Kites</span><span class="pl-artist">The Lumen Choir</span></span>
        <span class="pl-len">0:24</span></button></li>
    </ol>
  </div>
</section></div>
CSS
/* Playlist players on Bootstrap: a .card with .btn controls, a .form-range seek bar and a .list-group of tracks.
   The script sets --p (0 to 1) on .pl, toggles [data-playing] and moves aria-current between tracks. */
.pl{--pl-accent:#047857;--pl-soft:#ecfdf5;--pl-ink:#0f172a;--pl-muted:#475569;--pl-track:#8391a7;--pl-thumb:14px;--pl-h:4px;--p:0;
  --bs-card-border-color:#e2e8f0;--bs-card-border-radius:22px;--bs-card-inner-border-radius:21px;
  color:var(--pl-ink);box-shadow:0 10px 30px rgba(15,23,42,.07);overflow:hidden}
.pl .btn{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;display:grid;place-items:center}
.pl .btn:focus-visible{outline:2px solid var(--pl-accent);outline-offset:2px;box-shadow:none}
.pl svg{display:block;width:20px;height:20px}
.pl-art{border-radius:14px;background:var(--art)}
.pl-eyebrow{font-size:.7rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--pl-accent)}
.pl-now{font-size:1.3rem;font-weight:800;letter-spacing:-.02em;margin-top:4px}
.pl-by{font-size:.9rem;color:var(--pl-muted);margin-top:2px}
.pl .pl-skip{--bs-btn-color:var(--pl-ink);--bs-btn-hover-bg:#f1f5f9;--bs-btn-hover-color:var(--pl-ink);--bs-btn-active-bg:#e2e8f0;width:40px;height:40px}
.pl .pl-play{--bs-btn-bg:var(--pl-accent);--bs-btn-color:#fff;--bs-btn-hover-bg:#065f46;--bs-btn-hover-color:#fff;
  --bs-btn-active-bg:#064e3b;--bs-btn-active-color:#fff;width:48px;height:48px;box-shadow:0 6px 16px rgba(4,120,87,.3)}
.pl .pl-play svg{width:22px;height:22px}
.pl .i-pause,.pl[data-playing] .i-play{display:none}
.pl[data-playing] .i-pause{display:block}
.pl-time{font-size:.78rem;font-variant-numeric:tabular-nums;color:var(--pl-muted);min-width:2.3em}
.pl .form-range{flex:1;min-width:0;width:auto;height:var(--pl-thumb)}
.pl .form-range::-webkit-slider-runnable-track{height:var(--pl-h);border-radius:999px;
  background:linear-gradient(to right,var(--pl-accent) 0 calc(var(--pl-thumb) / 2 + (100% - var(--pl-thumb)) * var(--p)),var(--pl-track) 0)}
.pl .form-range::-moz-range-track{height:var(--pl-h);border-radius:999px;
  background:linear-gradient(to right,var(--pl-accent) 0 calc(var(--pl-thumb) / 2 + (100% - var(--pl-thumb)) * var(--p)),var(--pl-track) 0)}
.pl .form-range::-webkit-slider-thumb{width:var(--pl-thumb);height:var(--pl-thumb);margin-top:calc((var(--pl-h) - var(--pl-thumb)) / 2);
  border:3px solid var(--pl-accent);background:#fff;box-sizing:border-box}
.pl .form-range::-moz-range-thumb{width:var(--pl-thumb);height:var(--pl-thumb);border:3px solid var(--pl-accent);background:#fff;box-sizing:border-box}
.pl .form-range:focus::-webkit-slider-thumb{box-shadow:none}
.pl .form-range:focus::-moz-range-thumb{box-shadow:none}
.pl .form-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--pl-accent)}
.pl .form-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--pl-accent)}
/* track list: .list-group-item-action buttons */
.pl .list-group{--bs-list-group-border-width:0;--bs-list-group-bg:transparent;--bs-list-group-color:var(--pl-ink);
  --bs-list-group-action-hover-bg:#f8fafc;--bs-list-group-action-active-bg:var(--pl-soft);--bs-list-group-action-color:var(--pl-ink);
  --bs-list-group-action-hover-color:var(--pl-ink);padding:8px}
.pl .list-group-item{padding:0}
.pl .pl-item{display:grid;grid-template-columns:28px 40px 1fr auto;align-items:center;gap:12px;padding:8px 12px;border:0;border-radius:12px;background:none}
.pl .pl-item[aria-current="true"]{background:var(--pl-soft)}
.pl .pl-item:focus-visible{outline:2px solid var(--pl-accent);outline-offset:-2px}
.pl-num{font-size:.85rem;color:var(--pl-muted);font-variant-numeric:tabular-nums;text-align:center}
.pl-item .pl-art{width:40px;height:40px;border-radius:8px}
.pl-name{display:block;font-weight:600;font-size:.95rem}
.pl-artist{display:block;font-size:.8rem;color:var(--pl-muted);margin-top:2px}
.pl-len{font-size:.8rem;color:var(--pl-muted);font-variant-numeric:tabular-nums}
.pl-item[aria-current="true"] .pl-name{color:#065f46}
/* the current row swaps its number for three small bars that move while playing */
.pl-eq{display:none;height:14px;align-items:flex-end;justify-content:center;gap:2px}
.pl-eq i{width:3px;height:100%;border-radius:1px;background:var(--pl-accent);transform-origin:bottom}
.pl-eq i:nth-child(2){height:60%}.pl-eq i:nth-child(3){height:80%}
.pl-item[aria-current="true"] .pl-n{display:none}
.pl-item[aria-current="true"] .pl-eq{display:flex}
@keyframes pl-bounce{50%{transform:scaleY(.35)}}
@media (prefers-reduced-motion:no-preference){
  .pl[data-playing] .pl-eq i{animation:pl-bounce .9s ease-in-out infinite}
  .pl[data-playing] .pl-eq i:nth-child(2){animation-duration:.7s}
  .pl[data-playing] .pl-eq i:nth-child(3){animation-duration:1.1s}
}
/* 1. album with track list */
.pl-head{display:grid;grid-template-columns:150px 1fr;gap:22px;padding:22px;border-bottom:1px solid #e2e8f0;align-items:center}
.pl-head .pl-art{aspect-ratio:1}
/* 2. side-by-side queue */
.pl-side{display:grid;grid-template-columns:280px 1fr}
.pl-main{padding:22px;border-right:1px solid #e2e8f0}
.pl-main .pl-art{aspect-ratio:1}
.pl-queue{padding:14px 6px}
.pl-qh{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:var(--pl-muted);padding:8px 16px 0}
@media (max-width:560px){
  .pl-head{grid-template-columns:96px 1fr;gap:16px;padding:16px}
  .pl-side{grid-template-columns:1fr}
  .pl-main{border-right:0;border-bottom:1px solid #e2e8f0}
  .pl-main .pl-art{aspect-ratio:2 / 1}
  .pl .pl-item{grid-template-columns:22px 36px 1fr auto;gap:10px;padding:8px}
  .pl-item .pl-art{width:36px;height:36px}
}
JavaScript
// Playlist players. Each [data-pl] holds one <audio> and a list of track buttons that carry
// data-src, data-name and data-artist. The current track button gets aria-current="true".
(() => {
  const fmt = s => { s = Math.floor(s || 0); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
  document.querySelectorAll('[data-pl]').forEach(root => {
    const $ = s => root.querySelector(s);
    const audio = $('audio'), play = $('[data-play]'), seek = $('[data-seek]'), cur = $('[data-cur]'), dur = $('[data-dur]');
    const cover = $('[data-cover]'), items = [...root.querySelectorAll('[data-src]')];
    let i = Math.max(0, items.findIndex(b => b.getAttribute('aria-current') === 'true')), raf = 0;
    const label = () => play.setAttribute('aria-label', (audio.paused ? 'Play ' : 'Pause ') + items[i].dataset.name);
    const length = () => audio.duration || +seek.max;
    const sync = () => {
      const t = audio.currentTime, d = length(), text = fmt(t) + ' of ' + fmt(d);
      seek.value = t;
      root.style.setProperty('--p', d ? t / d : 0);
      cur.textContent = fmt(t);
      if (seek.getAttribute('aria-valuetext') !== text) seek.setAttribute('aria-valuetext', text);
    };
    const tick = () => { sync(); raf = requestAnimationFrame(tick); };
    const meta = () => { seek.max = audio.duration; dur.textContent = fmt(audio.duration); sync(); };
    const start = () => {
      document.querySelectorAll('audio').forEach(a => { if (a !== audio) a.pause(); });
      audio.play().catch(() => {});
    };
    // Switch to track n: mark it current, swap the source, title, artist and cover art.
    const load = n => {
      i = (n + items.length) % items.length;
      const item = items[i], art = item.querySelector('[data-art]');
      items.forEach(b => b.removeAttribute('aria-current'));
      item.setAttribute('aria-current', 'true');
      audio.src = item.dataset.src;
      root.querySelectorAll('[data-show]').forEach(el => { el.textContent = item.dataset[el.dataset.show]; });
      if (cover && art) {
        const cs = getComputedStyle(art);
        ['backgroundImage', 'backgroundPosition', 'backgroundSize', 'backgroundRepeat'].forEach(p => { cover.style[p] = cs[p]; });
      }
      label();
    };
    play.addEventListener('click', () => (audio.paused ? start() : audio.pause()));
    $('[data-prev]').addEventListener('click', () => {
      if (audio.currentTime > 3) audio.currentTime = 0; else load(i - 1);
      start();
    });
    $('[data-next]').addEventListener('click', () => { load(i + 1); start(); });
    items.forEach((item, n) => item.addEventListener('click', () => {
      if (n === i) return play.click();
      load(n); start();
    }));
    audio.addEventListener('play', () => { root.setAttribute('data-playing', ''); label(); tick(); });
    audio.addEventListener('pause', () => { root.removeAttribute('data-playing'); label(); cancelAnimationFrame(raf); sync(); });
    audio.addEventListener('ended', () => { if (i < items.length - 1) { load(i + 1); start(); } });
    audio.addEventListener('loadedmetadata', meta);
    if (audio.readyState > 0) meta();
    seek.addEventListener('input', () => { audio.currentTime = seek.value; sync(); });
    // Arrow keys jump 5 seconds; Home and End work natively.
    seek.addEventListener('keydown', e => {
      const step = { ArrowLeft: -5, ArrowDown: -5, ArrowRight: 5, ArrowUp: 5 }[e.key];
      if (!step) return;
      e.preventDefault();
      audio.currentTime = Math.min(Math.max(audio.currentTime + step, 0), length());
      sync();
    });
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#56657a] uppercase">Album with track list</span>
<section class="group overflow-hidden rounded-[22px] border border-[#e2e8f0] bg-white text-[#0f172a] shadow-[0_10px_30px_rgba(15,23,42,.07)] [--p:0]" data-pl aria-label="Low Tide Radio playlist">
  <audio src="../audio/track-1.mp3" preload="metadata"></audio>
  <div class="grid grid-cols-[150px_1fr] items-center gap-[22px] border-b border-[#e2e8f0] p-[22px] max-[560px]:grid-cols-[96px_1fr] max-[560px]:gap-4 max-[560px]:p-4">
    <div class="aspect-square rounded-[14px] [background:radial-gradient(circle_at_68%_40%,#fde68a_0_11%,transparent_11.5%),repeating-linear-gradient(180deg,transparent_0_6%,rgba(253,230,138,.3)_6%_7.5%)_bottom/100%_42%_no-repeat,linear-gradient(180deg,#fb923c,#e11d48_58%,#312e81_58%,#1e1b4b)]" data-cover aria-hidden="true"></div>
    <div class="flex min-w-0 flex-col gap-4">
      <div><p class="text-[.7rem] font-bold tracking-[.12em] text-pl-accent uppercase">Playlist · Low Tide Radio</p><p class="mt-1 text-[1.3rem] font-extrabold tracking-[-.02em]" data-show="name">Harbour Lights</p><p class="mt-0.5 text-[.9rem] text-pl-muted" data-show="artist">Juniper Vale</p></div>
      <div class="flex items-center gap-2">
        <button type="button" class="grid size-10 cursor-pointer place-items-center rounded-full text-[#0f172a] hover:bg-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pl-accent" data-prev aria-label="Previous track"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
        <button type="button" class="grid size-12 cursor-pointer place-items-center rounded-full bg-pl-accent text-white shadow-[0_6px_16px_rgba(4,120,87,.3)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pl-accent" data-play aria-label="Play Harbour Lights"><svg class="block size-[22px] group-data-playing:hidden" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="hidden size-[22px] group-data-playing:block" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
        <button type="button" class="grid size-10 cursor-pointer place-items-center rounded-full text-[#0f172a] hover:bg-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pl-accent" data-next aria-label="Next track"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
      </div>
      <div class="flex items-center gap-2">
        <span class="min-w-[2.3em] text-[.78rem] text-pl-muted tabular-nums" data-cur>0:00</span>
        <input type="range" class="flex-1 block h-[14px] min-w-0 cursor-pointer appearance-none bg-transparent [--f:var(--p)] focus-visible:outline-none [&::-webkit-slider-runnable-track]:h-[4px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-pl-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-pl-track)_0)] [&::-moz-range-track]:h-[4px] [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-pl-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-pl-track)_0)] [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[14px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#fff] [&::-webkit-slider-thumb]:box-border [&::-webkit-slider-thumb]:border-[3px] [&::-webkit-slider-thumb]:border-solid [&::-webkit-slider-thumb]:border-[color:var(--color-pl-accent)] focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-pl-accent)] [&::-moz-range-thumb]:size-[14px] [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[#fff] [&::-moz-range-thumb]:box-border [&::-moz-range-thumb]:border-[3px] [&::-moz-range-thumb]:border-solid [&::-moz-range-thumb]:border-[color:var(--color-pl-accent)] focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-pl-accent)]" data-seek min="0" max="24" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:24">
        <span class="min-w-[2.3em] text-[.78rem] text-pl-muted tabular-nums text-right" data-dur>0:24</span>
      </div>
    </div>
  </div>
  <ol class="p-2">
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale" aria-current="true">
      <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">1</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
      <span class="size-10 rounded-lg max-[560px]:size-9 [background:radial-gradient(circle_at_68%_40%,#fde68a_0_11%,transparent_11.5%),repeating-linear-gradient(180deg,transparent_0_6%,rgba(253,230,138,.3)_6%_7.5%)_bottom/100%_42%_no-repeat,linear-gradient(180deg,#fb923c,#e11d48_58%,#312e81_58%,#1e1b4b)]" data-art></span>
      <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Harbour Lights</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">Juniper Vale</span></span>
      <span class="text-[.8rem] text-pl-muted tabular-nums">0:24</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir">
      <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">2</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
      <span class="size-10 rounded-lg max-[560px]:size-9 [background:linear-gradient(to_bottom_right,transparent_50%,#fde047_50%)_37.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_bottom_left,transparent_50%,#f97316_50%)_62.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_top_right,transparent_50%,#fb923c_50%)_37.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_top_left,transparent_50%,#facc15_50%)_62.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_bottom_right,transparent_calc(50%_-_1px),rgba(255,255,255,.85)_0_calc(50%_+_1px),transparent_0)_41.86%_100%/14%_20%_no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6_55%,#84cc16)]" data-art></span>
      <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Paper Kites</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">The Lumen Choir</span></span>
      <span class="text-[.8rem] text-pl-muted tabular-nums">0:24</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch">
      <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">3</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
      <span class="size-10 rounded-lg max-[560px]:size-9 [background:radial-gradient(circle_at_68%_30%,#fef3c7_0_9%,rgba(254,243,199,.16)_9.5%_15%,transparent_15.5%),radial-gradient(circle_at_20%_25%,#fff_0_1.2%,transparent_2%),radial-gradient(circle_at_38%_55%,#fff_0_.9%,transparent_1.6%),radial-gradient(circle_at_86%_64%,#fff_0_1%,transparent_1.7%),radial-gradient(circle_at_12%_70%,#c4b5fd_0_1%,transparent_1.7%),linear-gradient(180deg,#0f172a,#312e81_70%,#7c3aed)]" data-art></span>
      <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Night Bus to Nowhere</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">Orla Finch</span></span>
      <span class="text-[.8rem] text-pl-muted tabular-nums">0:30</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">4</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
      <span class="size-10 rounded-lg max-[560px]:size-9 [background:radial-gradient(circle_at_50%_100%,#fff7ed_0_24%,#fdba74_24%_27%,transparent_27.5%),linear-gradient(180deg,#7c2d12_0_22%,#9a3412_22%_40%,#c2410c_40%_56%,#ea580c_56%_70%,#f59e0b_70%_84%,#fcd34d_84%)]" data-art></span>
      <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Copper Sky</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">Milo & the Tidelines</span></span>
      <span class="text-[.8rem] text-pl-muted tabular-nums">0:29</span></button></li>
  </ol>
</section></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#56657a] uppercase">Side-by-side queue</span>
<section class="group overflow-hidden rounded-[22px] border border-[#e2e8f0] bg-white text-[#0f172a] shadow-[0_10px_30px_rgba(15,23,42,.07)] [--p:0] grid grid-cols-[280px_1fr] max-[560px]:grid-cols-1" data-pl aria-label="Late night queue">
  <audio src="../audio/track-3.mp3" preload="metadata"></audio>
  <div class="flex flex-col gap-4 border-r border-[#e2e8f0] p-[22px] max-[560px]:border-r-0 max-[560px]:border-b">
    <div class="aspect-square rounded-[14px] [background:radial-gradient(circle_at_68%_30%,#fef3c7_0_9%,rgba(254,243,199,.16)_9.5%_15%,transparent_15.5%),radial-gradient(circle_at_20%_25%,#fff_0_1.2%,transparent_2%),radial-gradient(circle_at_38%_55%,#fff_0_.9%,transparent_1.6%),radial-gradient(circle_at_86%_64%,#fff_0_1%,transparent_1.7%),radial-gradient(circle_at_12%_70%,#c4b5fd_0_1%,transparent_1.7%),linear-gradient(180deg,#0f172a,#312e81_70%,#7c3aed)] max-[560px]:aspect-[2/1]" data-cover aria-hidden="true"></div>
    <div><p class="mt-1 text-[1.3rem] font-extrabold tracking-[-.02em]" data-show="name">Night Bus to Nowhere</p><p class="mt-0.5 text-[.9rem] text-pl-muted" data-show="artist">Orla Finch</p></div>
    <div class="flex items-center gap-2">
      <span class="min-w-[2.3em] text-[.78rem] text-pl-muted tabular-nums" data-cur>0:00</span>
      <input type="range" class="flex-1 block h-[14px] min-w-0 cursor-pointer appearance-none bg-transparent [--f:var(--p)] focus-visible:outline-none [&::-webkit-slider-runnable-track]:h-[4px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-pl-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-pl-track)_0)] [&::-moz-range-track]:h-[4px] [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-pl-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-pl-track)_0)] [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[14px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#fff] [&::-webkit-slider-thumb]:box-border [&::-webkit-slider-thumb]:border-[3px] [&::-webkit-slider-thumb]:border-solid [&::-webkit-slider-thumb]:border-[color:var(--color-pl-accent)] focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-pl-accent)] [&::-moz-range-thumb]:size-[14px] [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[#fff] [&::-moz-range-thumb]:box-border [&::-moz-range-thumb]:border-[3px] [&::-moz-range-thumb]:border-solid [&::-moz-range-thumb]:border-[color:var(--color-pl-accent)] focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-pl-accent)]" data-seek min="0" max="30" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:30">
      <span class="min-w-[2.3em] text-[.78rem] text-pl-muted tabular-nums text-right" data-dur>0:30</span>
    </div>
    <div class="flex items-center justify-center gap-2">
      <button type="button" class="grid size-10 cursor-pointer place-items-center rounded-full text-[#0f172a] hover:bg-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pl-accent" data-prev aria-label="Previous track"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
      <button type="button" class="grid size-12 cursor-pointer place-items-center rounded-full bg-pl-accent text-white shadow-[0_6px_16px_rgba(4,120,87,.3)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pl-accent" data-play aria-label="Play Night Bus to Nowhere"><svg class="block size-[22px] group-data-playing:hidden" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="hidden size-[22px] group-data-playing:block" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
      <button type="button" class="grid size-10 cursor-pointer place-items-center rounded-full text-[#0f172a] hover:bg-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pl-accent" data-next aria-label="Next track"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
    </div>
  </div>
  <div class="px-1.5 py-3.5">
    <p class="px-4 pt-2 text-[.75rem] tracking-[.12em] text-pl-muted uppercase">Queue</p>
    <ol class="p-2">
      <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch" aria-current="true">
        <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">1</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
        <span class="size-10 rounded-lg max-[560px]:size-9 [background:radial-gradient(circle_at_68%_30%,#fef3c7_0_9%,rgba(254,243,199,.16)_9.5%_15%,transparent_15.5%),radial-gradient(circle_at_20%_25%,#fff_0_1.2%,transparent_2%),radial-gradient(circle_at_38%_55%,#fff_0_.9%,transparent_1.6%),radial-gradient(circle_at_86%_64%,#fff_0_1%,transparent_1.7%),radial-gradient(circle_at_12%_70%,#c4b5fd_0_1%,transparent_1.7%),linear-gradient(180deg,#0f172a,#312e81_70%,#7c3aed)]" data-art></span>
        <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Night Bus to Nowhere</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">Orla Finch</span></span>
        <span class="text-[.8rem] text-pl-muted tabular-nums">0:30</span></button></li>
      <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
        <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">2</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
        <span class="size-10 rounded-lg max-[560px]:size-9 [background:radial-gradient(circle_at_50%_100%,#fff7ed_0_24%,#fdba74_24%_27%,transparent_27.5%),linear-gradient(180deg,#7c2d12_0_22%,#9a3412_22%_40%,#c2410c_40%_56%,#ea580c_56%_70%,#f59e0b_70%_84%,#fcd34d_84%)]" data-art></span>
        <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Copper Sky</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">Milo & the Tidelines</span></span>
        <span class="text-[.8rem] text-pl-muted tabular-nums">0:29</span></button></li>
      <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[28px_40px_1fr_auto] items-center gap-3 rounded-xl px-3 py-2 text-left hover:bg-[#f8fafc] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-pl-accent aria-[current=true]:bg-pl-soft max-[560px]:grid-cols-[22px_36px_1fr_auto] max-[560px]:gap-2.5 max-[560px]:p-2" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir">
        <span class="text-center text-[.85rem] text-pl-muted tabular-nums"><span class="group-aria-[current=true]/item:hidden">3</span><span class="hidden h-3.5 items-end justify-center gap-0.5 group-aria-[current=true]/item:flex" aria-hidden="true"><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-full motion-safe:group-data-playing:animate-pl-bounce"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[60%] motion-safe:group-data-playing:animate-pl-bounce-2"></i><i class="w-[3px] origin-bottom rounded-[1px] bg-pl-accent h-[80%] motion-safe:group-data-playing:animate-pl-bounce-3"></i></span></span>
        <span class="size-10 rounded-lg max-[560px]:size-9 [background:linear-gradient(to_bottom_right,transparent_50%,#fde047_50%)_37.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_bottom_left,transparent_50%,#f97316_50%)_62.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_top_right,transparent_50%,#fb923c_50%)_37.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_top_left,transparent_50%,#facc15_50%)_62.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_bottom_right,transparent_calc(50%_-_1px),rgba(255,255,255,.85)_0_calc(50%_+_1px),transparent_0)_41.86%_100%/14%_20%_no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6_55%,#84cc16)]" data-art></span>
        <span><span class="block text-[.95rem] font-semibold group-aria-[current=true]/item:text-[#065f46]">Paper Kites</span><span class="mt-0.5 block text-[.8rem] text-pl-muted">The Lumen Choir</span></span>
        <span class="text-[.8rem] text-pl-muted tabular-nums">0:24</span></button></li>
    </ol>
  </div>
</section></div>
CSS
@theme {
  --color-pl-accent: #047857;
  --color-pl-soft: #ecfdf5;
  --color-pl-muted: #475569;
  --color-pl-track: #8391a7;
  --animate-pl-bounce: pl-bounce .9s ease-in-out infinite;
  --animate-pl-bounce-2: pl-bounce .7s ease-in-out infinite;
  --animate-pl-bounce-3: pl-bounce 1.1s ease-in-out infinite;
  @keyframes pl-bounce { 50% { transform: scaleY(.35); } }
}
JavaScript
// Playlist players. Each [data-pl] holds one <audio> and a list of track buttons that carry
// data-src, data-name and data-artist. The current track button gets aria-current="true".
(() => {
  const fmt = s => { s = Math.floor(s || 0); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
  document.querySelectorAll('[data-pl]').forEach(root => {
    const $ = s => root.querySelector(s);
    const audio = $('audio'), play = $('[data-play]'), seek = $('[data-seek]'), cur = $('[data-cur]'), dur = $('[data-dur]');
    const cover = $('[data-cover]'), items = [...root.querySelectorAll('[data-src]')];
    let i = Math.max(0, items.findIndex(b => b.getAttribute('aria-current') === 'true')), raf = 0;
    const label = () => play.setAttribute('aria-label', (audio.paused ? 'Play ' : 'Pause ') + items[i].dataset.name);
    const length = () => audio.duration || +seek.max;
    const sync = () => {
      const t = audio.currentTime, d = length(), text = fmt(t) + ' of ' + fmt(d);
      seek.value = t;
      root.style.setProperty('--p', d ? t / d : 0);
      cur.textContent = fmt(t);
      if (seek.getAttribute('aria-valuetext') !== text) seek.setAttribute('aria-valuetext', text);
    };
    const tick = () => { sync(); raf = requestAnimationFrame(tick); };
    const meta = () => { seek.max = audio.duration; dur.textContent = fmt(audio.duration); sync(); };
    const start = () => {
      document.querySelectorAll('audio').forEach(a => { if (a !== audio) a.pause(); });
      audio.play().catch(() => {});
    };
    // Switch to track n: mark it current, swap the source, title, artist and cover art.
    const load = n => {
      i = (n + items.length) % items.length;
      const item = items[i], art = item.querySelector('[data-art]');
      items.forEach(b => b.removeAttribute('aria-current'));
      item.setAttribute('aria-current', 'true');
      audio.src = item.dataset.src;
      root.querySelectorAll('[data-show]').forEach(el => { el.textContent = item.dataset[el.dataset.show]; });
      if (cover && art) {
        const cs = getComputedStyle(art);
        ['backgroundImage', 'backgroundPosition', 'backgroundSize', 'backgroundRepeat'].forEach(p => { cover.style[p] = cs[p]; });
      }
      label();
    };
    play.addEventListener('click', () => (audio.paused ? start() : audio.pause()));
    $('[data-prev]').addEventListener('click', () => {
      if (audio.currentTime > 3) audio.currentTime = 0; else load(i - 1);
      start();
    });
    $('[data-next]').addEventListener('click', () => { load(i + 1); start(); });
    items.forEach((item, n) => item.addEventListener('click', () => {
      if (n === i) return play.click();
      load(n); start();
    }));
    audio.addEventListener('play', () => { root.setAttribute('data-playing', ''); label(); tick(); });
    audio.addEventListener('pause', () => { root.removeAttribute('data-playing'); label(); cancelAnimationFrame(raf); sync(); });
    audio.addEventListener('ended', () => { if (i < items.length - 1) { load(i + 1); start(); } });
    audio.addEventListener('loadedmetadata', meta);
    if (audio.readyState > 0) meta();
    seek.addEventListener('input', () => { audio.currentTime = seek.value; sync(); });
    // Arrow keys jump 5 seconds; Home and End work natively.
    seek.addEventListener('keydown', e => {
      const step = { ArrowLeft: -5, ArrowDown: -5, ArrowRight: 5, ArrowUp: 5 }[e.key];
      if (!step) return;
      e.preventDefault();
      audio.currentTime = Math.min(Math.max(audio.currentTime + step, 0), length());
      sync();
    });
  });
})();

About this set

Two players that work through a list of tracks. The album with track list has a header with a cover, the playlist name, the current title and artist, previous, play and next buttons and a seek slider, with an ordered list of four tracks underneath; each row shows the track number, a thumbnail, the title, the artist and the length. The side-by-side queue puts a large cover and the controls on the left and a three-track queue on the right, and stacks into one column under 560 pixels. Each track row is a button carrying data-src, data-name and data-artist. Clicking a row loads that file, copies the row’s thumbnail into the big cover, rewrites the title and artist and starts playing; clicking the current row plays or pauses it. Previous restarts the track when more than three seconds have played and otherwise goes back one, and when a track ends the next one starts. The current row gets aria-current, a tint and three small bars in place of its number that bounce while audio plays. The Bootstrap version uses a .list-group of .list-group-item-action buttons.

What’s included

  • Album layout with a track list and a side-by-side queue layout
  • Track rows are buttons with data-src, data-name and data-artist
  • Current track marked with aria-current and a tinted row
  • Previous, next and automatic advance at the end of a track
  • Now-playing bars that move only while audio plays
  • Bootstrap .list-group and Tailwind aria-[current=true] variants

Accessibility

Each player is a labelled section, and the track rows are buttons inside an ordered list, so the list announces its length and each row reads out its title, artist and duration. The current row carries aria-current="true", the play button's label names the current track, and the seek slider reports text such as 0:07 of 0:30. Focus draws a 2px outline, inset on the rows, and the bouncing bars only animate when prefers-reduced-motion is not set.

Customise it

Change --pl-accent, --pl-soft and --pl-track on .pl; --pl-soft is the tint behind the current row. In Bootstrap the list colours come from the --bs-list-group-* variables set on .pl .list-group, and in Tailwind from the --color-pl-* theme colours.