Open demo

CSS code

HTML
<div><span class="lbl">Layers panel</span>
<section class="dk" data-dd aria-labelledby="dk-layers-t">
  <div class="dk-head"><h2 id="dk-layers-t">Layers · Landing page</h2><p id="dk-hint1">Drag a layer, or focus its handle, press Space and use the arrow keys. The top layer is drawn in front.</p></div>
  <ul class="dk-list" data-dd-list="layers">
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint1"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m10 2 3 3-3 3-3-3zM10 12l3 3-3 3-3-3zM5 7l3 3-3 3-3-3zM15 7l3 3-3 3-3-3z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg><span class="dk-name" data-dd-name>Navigation bar</span><span class="dk-type">Component</span><span class="dk-z" data-z>0</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint1"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M4 5V3.5h12V5M10 3.5v13M7.5 16.5h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="dk-name" data-dd-name>Hero title</span><span class="dk-type">Text</span><span class="dk-z" data-z>0</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint1"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><rect x="2.5" y="3.5" width="15" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="m3 14 4.5-4.5 3.5 3.5 2-2 4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="dk-name" data-dd-name>Hero photo</span><span class="dk-type">Image</span><span class="dk-z" data-z>0</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint1"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m10 2 3 3-3 3-3-3zM10 12l3 3-3 3-3-3zM5 7l3 3-3 3-3-3zM15 7l3 3-3 3-3-3z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg><span class="dk-name" data-dd-name>Sign-up button</span><span class="dk-type">Component</span><span class="dk-z" data-z>0</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint1"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 2v16M14 2v16M2 6h16M2 14h16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="dk-name" data-dd-name>Feature grid</span><span class="dk-type">Frame</span><span class="dk-z" data-z>0</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint1"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><rect x="3" y="4" width="14" height="12" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><span class="dk-name" data-dd-name>Page background</span><span class="dk-type">Rectangle</span><span class="dk-z" data-z>0</span></li>
  </ul>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>

<div><span class="lbl">Up next queue</span>
<section class="dk" data-dd aria-labelledby="dk-q-t">
  <div class="dk-head"><h2 id="dk-q-t">Queue</h2><p id="dk-hint2">Drag a song to change what plays next, or focus its handle and use Space and the arrow keys.</p></div>
  <div class="dk-now"><span class="dk-art a0"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 4v12l10-6z"/></svg></span><span class="dk-track"><span class="dk-kicker">Now playing</span><b>Afterglow</b><small>Mira Sol</small></span><span class="dk-time">2:14 / 3:50</span></div>
  <p class="dk-sub">Up next</p>
  <ol class="dk-list" data-dd-list="queue" aria-label="Up next">
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint2"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a1" aria-hidden="true"></span><span class="dk-track"><b data-dd-name>Glass Harbour</b><small>Nadia Vale</small></span><span class="dk-time">3:42</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint2"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a2" aria-hidden="true"></span><span class="dk-track"><b data-dd-name>Night Bus</b><small>The Lindens</small></span><span class="dk-time">4:05</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint2"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a3" aria-hidden="true"></span><span class="dk-track"><b data-dd-name>Saltwater</b><small>Ora Kim</small></span><span class="dk-time">3:18</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint2"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a4" aria-hidden="true"></span><span class="dk-track"><b data-dd-name>Low Orbit</b><small>Felix Ames</small></span><span class="dk-time">5:01</span></li>
    <li class="dk-row" data-dd-item><button class="dk-handle" type="button" data-dd-handle aria-describedby="dk-hint2"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a5" aria-hidden="true"></span><span class="dk-track"><b data-dd-name>Paper Moon</b><small>June and the Tides</small></span><span class="dk-time">3:56</span></li>
  </ol>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>
CSS
/* Dark drag and drop: the same pointer and keyboard script on near-black panels. Colours are checked for contrast
   on #151821: body text #e6e9ef, secondary #9aa3b5, accent #8b9cff for focus rings, drop targets and the lifted item. */
.dk{--dk-panel:#151821;--dk-row:#1b1f2a;--dk-line:#262b36;--dk-ink:#e6e9ef;--dk-muted:#9aa3b5;--dk-accent:#8b9cff;--dk-green:#34d399;
  background:var(--dk-panel);border:1px solid var(--dk-line);border-radius:16px;padding:14px;color:var(--dk-ink);box-shadow:0 24px 48px -24px rgba(0,0,0,.6)}
.dk-head{display:flex;align-items:baseline;justify-content:space-between;gap:4px 12px;flex-wrap:wrap;margin:2px 4px 12px}
.dk-head h2{font-size:.95rem;letter-spacing:-.01em}
.dk-head p{font-size:.78rem;color:var(--dk-muted)}
.dk-list{list-style:none;display:grid;gap:4px}
.dk-row{display:flex;align-items:center;gap:10px;padding:6px 10px 6px 4px;border:1px solid transparent;border-radius:10px;background:var(--dk-row);cursor:grab;user-select:none}
.dk-row:hover{border-color:var(--dk-line)}
.dk-handle{flex:none;display:grid;place-items:center;width:28px;height:30px;border:0;border-radius:7px;background:none;color:var(--dk-muted);cursor:grab;touch-action:none}
.dk-handle:hover{background:#262b36;color:var(--dk-ink)}
.dk-handle:focus-visible{outline:2px solid var(--dk-accent);outline-offset:1px}
.dk-handle svg{width:16px;height:16px}
.dk-ico{flex:none;width:18px;height:18px;color:var(--dk-accent)}
.dk-name{flex:1;min-width:0;font-size:.86rem;font-weight:600}
.dk-type{font-size:.72rem;color:var(--dk-muted)}
.dk-z{min-width:24px;padding:1px 6px;border-radius:6px;background:#262b36;font-size:.7rem;font-weight:700;color:var(--dk-muted);text-align:center;font-variant-numeric:tabular-nums}
.dk-now{display:flex;align-items:center;gap:12px;padding:10px;margin-bottom:10px;border-radius:12px;background:linear-gradient(90deg,rgba(52,211,153,.14),rgba(52,211,153,0))}
.dk-art{flex:none;width:40px;height:40px;border-radius:8px}
.dk-now .dk-art{width:52px;height:52px;display:grid;place-items:center;color:#fff}
.dk-now .dk-art svg{width:20px;height:20px}
.dk-kicker{font-size:.68rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--dk-green)}
.dk-track{flex:1;min-width:0;display:grid;line-height:1.3}
.dk-track b{font-size:.88rem;font-weight:600}
.dk-track small{font-size:.76rem;color:var(--dk-muted)}
.dk-time{font-size:.76rem;color:var(--dk-muted);font-variant-numeric:tabular-nums}
.dk-sub{font-size:.7rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--dk-muted);margin:0 4px 6px}
.dk-art.a0{background:linear-gradient(135deg,#f97316,#db2777)}.dk-art.a1{background:linear-gradient(135deg,#22d3ee,#6366f1)}.dk-art.a2{background:linear-gradient(135deg,#a3e635,#0d9488)}.dk-art.a3{background:linear-gradient(135deg,#fbbf24,#ef4444)}.dk-art.a4{background:linear-gradient(135deg,#818cf8,#c026d3)}.dk-art.a5{background:linear-gradient(135deg,#94a3b8,#334155)}
/* states set by the script */
.dk-row[data-dragging]{background:rgba(139,156,255,.08);border:1px dashed #6b78c9}
.dk-row[data-dragging]>*{visibility:hidden}
.dk-row[data-dd-ghost]{background:#232838;border-color:var(--dk-accent);box-shadow:0 18px 40px -10px rgba(0,0,0,.75),0 0 0 1px rgba(139,156,255,.3);rotate:-1deg}
.dk-row[data-grabbed]{background:#232838;border-color:var(--dk-accent);box-shadow:0 0 0 1px var(--dk-accent),0 0 22px -4px rgba(139,156,255,.55)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@media (prefers-reduced-motion:reduce){.dk-row[data-dd-ghost]{rotate:none}}
JavaScript
// Layers: number the stacking order, top layer first, so the z-index a row will get is visible.
document.addEventListener('dd:change', e => {
  const rows = e.target.querySelectorAll('[data-z]');
  rows.forEach((n, i) => { n.textContent = rows.length - i; });
});
// Drag and drop with pointer events (mouse, touch and pen) plus a keyboard mode on every handle:
// Space or Enter picks an item up, the arrow keys move it, Space or Enter drops it, Escape puts it back.
// Every move is announced in the component's aria-live region. Markup contract: [data-dd] root,
// [data-dd-list="group"] lists, [data-dd-item] items with a [data-dd-name] and a [data-dd-handle] button.
// data-dd="grid" or "wrap" compares the pointer horizontally; in a grid, Up and Down jump a whole row.
document.querySelectorAll('[data-dd]').forEach(root => {
  const grid = root.dataset.dd === 'grid', horiz = grid || root.dataset.dd === 'wrap', live = root.querySelector('[data-dd-live]');
  const items = l => [...l.children].filter(c => c.hasAttribute('data-dd-item'));
  const lists = l => [...root.querySelectorAll(`[data-dd-list="${l.dataset.ddList}"]`)];
  const name = el => el.querySelector('[data-dd-name]').textContent.trim();
  const full = l => !!l.dataset.ddMax && items(l).length >= +l.dataset.ddMax;
  const pos = el => { const l = el.parentElement, all = items(l);
    return (lists(l).length > 1 ? l.getAttribute('aria-label') + ', ' : '') + 'position ' + (all.indexOf(el) + 1) + ' of ' + all.length; };
  const say = msg => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };
  const clearOver = keep => root.querySelectorAll('[data-over]').forEach(l => l !== keep && l.removeAttribute('data-over'));
  const marks = () => root.querySelectorAll('[data-dd-list]').forEach(l => { const n = items(l).length, c = root.querySelector(`[data-dd-count="${l.id}"]`);
    l.toggleAttribute('data-empty', !n); l.toggleAttribute('data-full', full(l)); if (c) c.textContent = n; });
  const changed = () => {
    marks();
    root.querySelectorAll('[data-dd-item]').forEach(it => it.querySelector('[data-dd-handle]').setAttribute('aria-label', 'Move ' + name(it)));
    root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  };
  let drag, ghost, home, kb, kbh, sx, sy, ox, oy, pid, dragged, moving;
  const putBack = el => home[0].insertBefore(el, home[1] && home[1].parentNode === home[0] ? home[1] : null);

  root.addEventListener('pointerdown', e => {
    const item = e.target.closest('[data-dd-item]'), handle = e.target.closest('[data-dd-handle]');
    if (!item || e.button > 0 || kb) return;
    if (!handle && (e.pointerType !== 'mouse' || 'ddHandleOnly' in root.dataset || e.target.closest('a,button,input,select,textarea,label'))) return;
    e.preventDefault(); drag = item; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ghost = null;
  });
  root.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== pid) return;
    if (!ghost) { if (Math.hypot(e.clientX - sx, e.clientY - sy) < 6) return; start(); root.setPointerCapture(pid); }
    ghost.style.translate = `${e.clientX - ox}px ${e.clientY - oy}px`;
    if (e.clientY < 40) scrollBy(0, -12); else if (e.clientY > innerHeight - 40) scrollBy(0, 12);
    const hit = document.elementFromPoint(e.clientX, e.clientY), sel = `[data-dd-list="${home[0].dataset.ddList}"]`;
    const list = hit && (hit.closest(sel) || hit.closest('[data-dd-zone]')?.querySelector(sel)); // a [data-dd-zone] wrapper widens the target
    clearOver(list);
    if (!list || !root.contains(list) || drag.contains(list)) return;
    list.setAttribute('data-over', '');
    if (list !== drag.parentElement && full(list)) return;
    const over = items(list).find(c => c !== drag && c.contains(hit));
    if (over) { const r = over.getBoundingClientRect();
      const ref = (horiz ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2) ? over.nextSibling : over;
      if (ref !== drag && ref !== drag.nextSibling) { list.insertBefore(drag, ref); marks(); }
    } else if (list !== drag.parentElement && !drag.contains(hit)) { list.append(drag); marks(); }
  });
  const end = e => {
    if (!drag || e.pointerId !== pid) return;
    if (ghost) {
      ghost.remove(); drag.removeAttribute('data-dragging'); root.removeAttribute('data-dd-active'); document.body.style.cursor = ''; clearOver();
      if (e.type === 'pointercancel') putBack(drag); else say(name(drag) + ' moved to ' + pos(drag) + '.');
      changed(); dragged = true; setTimeout(() => { dragged = false; });
    }
    drag = null;
  };
  root.addEventListener('pointerup', end); root.addEventListener('pointercancel', end);
  function start() {
    const r = drag.getBoundingClientRect(); ox = sx - r.left; oy = sy - r.top; home = [drag.parentElement, drag.nextSibling];
    ghost = drag.cloneNode(true); ghost.removeAttribute('data-dd-item'); ghost.setAttribute('data-dd-ghost', ''); ghost.setAttribute('aria-hidden', 'true'); ghost.inert = true;
    ghost.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
    Object.assign(ghost.style, { position: 'fixed', left: 0, top: 0, width: r.width + 'px', height: r.height + 'px', margin: 0, zIndex: 999, pointerEvents: 'none' });
    drag.after(ghost); drag.setAttribute('data-dragging', ''); root.setAttribute('data-dd-active', ''); document.body.style.cursor = 'grabbing';
  }

  // Keyboard and screen reader mode: the handle is a toggle button (aria-pressed) that picks the item up.
  const finish = msg => { kb.removeAttribute('data-grabbed'); kbh.setAttribute('aria-pressed', 'false'); root.removeAttribute('data-dd-active'); clearOver(); kb = null; changed(); say(msg); };
  const move = fn => { moving = true; fn(); kbh.focus(); moving = false; kbh.scrollIntoView({ block: 'nearest' }); clearOver(kb.parentElement); kb.parentElement.setAttribute('data-over', ''); changed(); say(name(kb) + ' moved to ' + pos(kb) + '.'); };
  root.addEventListener('click', e => {
    const h = e.target.closest('[data-dd-handle]'); if (!h || dragged) return;
    const item = h.closest('[data-dd-item]');
    if (kb === item) return finish(name(item) + ' dropped, ' + pos(item) + '.');
    if (kb) finish(name(kb) + ' dropped, ' + pos(kb) + '.');
    kb = item; kbh = h; home = [item.parentElement, item.nextSibling];
    item.setAttribute('data-grabbed', ''); h.setAttribute('aria-pressed', 'true'); root.setAttribute('data-dd-active', ''); item.parentElement.setAttribute('data-over', ''); h.focus();
    say(name(item) + ' picked up, ' + pos(item) + '. Use the arrow keys to move it, Space to drop it, Escape to cancel.');
  });
  root.addEventListener('keydown', e => {
    if (!kb || e.target !== kbh) return;
    if (e.key === 'Escape') { e.preventDefault(); moving = true; putBack(kb); kbh.focus(); moving = false; return finish('Move cancelled. ' + name(kb) + ' returned to ' + pos(kb) + '.'); }
    const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }[e.key]; if (!d) return;
    e.preventDefault();
    const l = kb.parentElement, all = items(l), i = all.indexOf(kb), ls = lists(l), side = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
    if (side && ls.length > 1) {
      const t = ls[ls.indexOf(l) + d];
      if (!t) return say(name(kb) + ' is already in ' + l.getAttribute('aria-label') + ', the ' + (d < 0 ? 'first' : 'last') + ' list.');
      if (full(t)) return say(t.getAttribute('aria-label') + ' is full.');
      return move(() => t.insertBefore(kb, items(t)[i] || null));
    }
    const step = grid && !side ? d * all.filter(x => x.offsetTop === all[0].offsetTop).length : d, j = Math.max(0, Math.min(all.length - 1, i + step));
    if (j === i) return say(name(kb) + ' is already ' + (d < 0 ? 'first' : 'last') + '.');
    move(() => l.insertBefore(kb, j > i ? all[j].nextSibling : all[j]));
  });
  root.addEventListener('focusout', e => { if (kb && !moving && e.target === kbh) finish(name(kb) + ' dropped, ' + pos(kb) + '.'); });
  changed();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Layers panel</span>
<section class="dk card" data-bs-theme="dark" data-dd aria-labelledby="dk-layers-t">
  <p class="visually-hidden" aria-live="assertive" aria-atomic="true" data-dd-live></p>
  <div class="dk-head d-flex flex-wrap align-items-baseline justify-content-between column-gap-3 row-gap-1 mx-1 mb-3"><h2 class="fw-bold mb-0" id="dk-layers-t">Layers · Landing page</h2><p class="text-body-secondary mb-0" id="dk-hint1">Drag a layer, or focus its handle, press Space and use the arrow keys. The top layer is drawn in front.</p></div>
  <ul class="list-group" data-dd-list="layers">
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico flex-shrink-0" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m10 2 3 3-3 3-3-3zM10 12l3 3-3 3-3-3zM5 7l3 3-3 3-3-3zM15 7l3 3-3 3-3-3z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg><span class="dk-name flex-grow-1 fw-semibold" data-dd-name>Navigation bar</span><span class="dk-type text-body-secondary">Component</span><span class="dk-z badge" data-z>0</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico flex-shrink-0" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M4 5V3.5h12V5M10 3.5v13M7.5 16.5h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="dk-name flex-grow-1 fw-semibold" data-dd-name>Hero title</span><span class="dk-type text-body-secondary">Text</span><span class="dk-z badge" data-z>0</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico flex-shrink-0" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><rect x="2.5" y="3.5" width="15" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="m3 14 4.5-4.5 3.5 3.5 2-2 4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="dk-name flex-grow-1 fw-semibold" data-dd-name>Hero photo</span><span class="dk-type text-body-secondary">Image</span><span class="dk-z badge" data-z>0</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico flex-shrink-0" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m10 2 3 3-3 3-3-3zM10 12l3 3-3 3-3-3zM5 7l3 3-3 3-3-3zM15 7l3 3-3 3-3-3z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg><span class="dk-name flex-grow-1 fw-semibold" data-dd-name>Sign-up button</span><span class="dk-type text-body-secondary">Component</span><span class="dk-z badge" data-z>0</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico flex-shrink-0" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 2v16M14 2v16M2 6h16M2 14h16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="dk-name flex-grow-1 fw-semibold" data-dd-name>Feature grid</span><span class="dk-type text-body-secondary">Frame</span><span class="dk-z badge" data-z>0</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="dk-ico flex-shrink-0" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><rect x="3" y="4" width="14" height="12" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><span class="dk-name flex-grow-1 fw-semibold" data-dd-name>Page background</span><span class="dk-type text-body-secondary">Rectangle</span><span class="dk-z badge" data-z>0</span></li>
  </ul>
</section></div>

<div><span class="lbl">Up next queue</span>
<section class="dk card" data-bs-theme="dark" data-dd aria-labelledby="dk-q-t">
  <p class="visually-hidden" aria-live="assertive" aria-atomic="true" data-dd-live></p>
  <div class="dk-head d-flex flex-wrap align-items-baseline justify-content-between column-gap-3 row-gap-1 mx-1 mb-3"><h2 class="fw-bold mb-0" id="dk-q-t">Queue</h2><p class="text-body-secondary mb-0" id="dk-hint2">Drag a song to change what plays next, or focus its handle and use Space and the arrow keys.</p></div>
  <div class="dk-now d-flex align-items-center gap-3 mb-2"><span class="dk-art a0 d-grid flex-shrink-0"><svg class="m-auto" width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 4v12l10-6z"/></svg></span><span class="dk-track d-grid flex-grow-1"><span class="dk-kicker fw-bolder text-uppercase">Now playing</span><b>Afterglow</b><small class="text-body-secondary">Mira Sol</small></span><span class="dk-time text-body-secondary">2:14 / 3:50</span></div>
  <p class="dk-sub fw-bolder text-uppercase text-body-secondary mx-1 mb-2">Up next</p>
  <ol class="list-group" data-dd-list="queue" aria-label="Up next">
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a1 flex-shrink-0" aria-hidden="true"></span><span class="dk-track d-grid flex-grow-1"><b data-dd-name>Glass Harbour</b><small class="text-body-secondary">Nadia Vale</small></span><span class="dk-time text-body-secondary">3:42</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a2 flex-shrink-0" aria-hidden="true"></span><span class="dk-track d-grid flex-grow-1"><b data-dd-name>Night Bus</b><small class="text-body-secondary">The Lindens</small></span><span class="dk-time text-body-secondary">4:05</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a3 flex-shrink-0" aria-hidden="true"></span><span class="dk-track d-grid flex-grow-1"><b data-dd-name>Saltwater</b><small class="text-body-secondary">Ora Kim</small></span><span class="dk-time text-body-secondary">3:18</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a4 flex-shrink-0" aria-hidden="true"></span><span class="dk-track d-grid flex-grow-1"><b data-dd-name>Low Orbit</b><small class="text-body-secondary">Felix Ames</small></span><span class="dk-time text-body-secondary">5:01</span></li>
    <li class="list-group-item d-flex align-items-center gap-2" data-dd-item><button class="dk-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="dk-art a5 flex-shrink-0" aria-hidden="true"></span><span class="dk-track d-grid flex-grow-1"><b data-dd-name>Paper Moon</b><small class="text-body-secondary">June and the Tides</small></span><span class="dk-time text-body-secondary">3:56</span></li>
  </ol>
</section></div>
CSS
/* Dark drag and drop for Bootstrap 5: data-bs-theme="dark" on each .card, a .list-group for the rows and .badge for
   the stacking numbers; these rules set the panel colours and the states the script sets. */
.dk{--bs-body-bg:#151821;--bs-card-bg:#151821;--bs-card-border-color:#262b36;--bs-card-border-radius:16px;--bs-body-color:#e6e9ef;--bs-secondary-color:#9aa3b5;
  --dk-row:#1b1f2a;--dk-accent:#8b9cff;--dk-green:#34d399;padding:14px;color:#e6e9ef;box-shadow:0 24px 48px -24px rgba(0,0,0,.6)}
.dk-head h2{font-size:.95rem;letter-spacing:-.01em}
.dk-head p{font-size:.78rem}
.dk .list-group{--bs-list-group-bg:#1b1f2a;--bs-list-group-color:#e6e9ef;--bs-list-group-border-color:transparent;--bs-list-group-border-radius:10px;--bs-list-group-item-padding-x:10px;--bs-list-group-item-padding-y:6px;gap:4px}
.dk .list-group-item{border-radius:10px!important;border-width:1px!important;padding-left:4px;cursor:grab;user-select:none}
.dk .list-group-item:hover{border-color:#262b36}
.dk-handle{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-color:#9aa3b5;--bs-btn-hover-bg:#262b36;--bs-btn-hover-color:#e6e9ef;--bs-btn-active-bg:#262b36;
  --bs-btn-border-width:0;--bs-btn-border-radius:7px;width:28px;height:30px;cursor:grab;touch-action:none}
.dk-handle:focus-visible{outline:2px solid var(--dk-accent);outline-offset:1px;box-shadow:none}
.dk-ico{color:var(--dk-accent)}
.dk-name{font-size:.86rem}
.dk-type,.dk-time{font-size:.72rem}
.dk-time{font-size:.76rem;font-variant-numeric:tabular-nums}
.dk .badge.dk-z{min-width:24px;background:#262b36;color:#9aa3b5;font-size:.7rem;font-variant-numeric:tabular-nums}
.dk-now{padding:10px;border-radius:12px;background:linear-gradient(90deg,rgba(52,211,153,.14),rgba(52,211,153,0))}
.dk-art{width:40px;height:40px;border-radius:8px}
.dk-now .dk-art{width:52px;height:52px;color:#fff}
.dk-kicker{font-size:.68rem;letter-spacing:.1em;color:var(--dk-green)}
.dk-track{line-height:1.3}
.dk-track b{font-size:.88rem;font-weight:600}
.dk-track small{font-size:.76rem}
.dk-sub{font-size:.7rem;letter-spacing:.1em}
.dk-art.a0{background:linear-gradient(135deg,#f97316,#db2777)}.dk-art.a1{background:linear-gradient(135deg,#22d3ee,#6366f1)}.dk-art.a2{background:linear-gradient(135deg,#a3e635,#0d9488)}.dk-art.a3{background:linear-gradient(135deg,#fbbf24,#ef4444)}.dk-art.a4{background:linear-gradient(135deg,#818cf8,#c026d3)}.dk-art.a5{background:linear-gradient(135deg,#94a3b8,#334155)}
.dk .list-group-item[data-dragging]{background:rgba(139,156,255,.08);border:1px dashed #6b78c9!important}
.dk .list-group-item[data-dragging]>*{visibility:hidden}
.dk .list-group-item[data-dd-ghost]{background:#232838;border-color:var(--dk-accent)!important;box-shadow:0 18px 40px -10px rgba(0,0,0,.75),0 0 0 1px rgba(139,156,255,.3);rotate:-1deg}
.dk .list-group-item[data-grabbed]{background:#232838;border-color:var(--dk-accent)!important;box-shadow:0 0 0 1px var(--dk-accent),0 0 22px -4px rgba(139,156,255,.55)}
@media (prefers-reduced-motion:reduce){.dk .list-group-item[data-dd-ghost]{rotate:none}}
JavaScript
// Layers: number the stacking order, top layer first, so the z-index a row will get is visible.
document.addEventListener('dd:change', e => {
  const rows = e.target.querySelectorAll('[data-z]');
  rows.forEach((n, i) => { n.textContent = rows.length - i; });
});
// Drag and drop with pointer events (mouse, touch and pen) plus a keyboard mode on every handle:
// Space or Enter picks an item up, the arrow keys move it, Space or Enter drops it, Escape puts it back.
// Every move is announced in the component's aria-live region. Markup contract: [data-dd] root,
// [data-dd-list="group"] lists, [data-dd-item] items with a [data-dd-name] and a [data-dd-handle] button.
// data-dd="grid" or "wrap" compares the pointer horizontally; in a grid, Up and Down jump a whole row.
document.querySelectorAll('[data-dd]').forEach(root => {
  const grid = root.dataset.dd === 'grid', horiz = grid || root.dataset.dd === 'wrap', live = root.querySelector('[data-dd-live]');
  const items = l => [...l.children].filter(c => c.hasAttribute('data-dd-item'));
  const lists = l => [...root.querySelectorAll(`[data-dd-list="${l.dataset.ddList}"]`)];
  const name = el => el.querySelector('[data-dd-name]').textContent.trim();
  const full = l => !!l.dataset.ddMax && items(l).length >= +l.dataset.ddMax;
  const pos = el => { const l = el.parentElement, all = items(l);
    return (lists(l).length > 1 ? l.getAttribute('aria-label') + ', ' : '') + 'position ' + (all.indexOf(el) + 1) + ' of ' + all.length; };
  const say = msg => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };
  const clearOver = keep => root.querySelectorAll('[data-over]').forEach(l => l !== keep && l.removeAttribute('data-over'));
  const marks = () => root.querySelectorAll('[data-dd-list]').forEach(l => { const n = items(l).length, c = root.querySelector(`[data-dd-count="${l.id}"]`);
    l.toggleAttribute('data-empty', !n); l.toggleAttribute('data-full', full(l)); if (c) c.textContent = n; });
  const changed = () => {
    marks();
    root.querySelectorAll('[data-dd-item]').forEach(it => it.querySelector('[data-dd-handle]').setAttribute('aria-label', 'Move ' + name(it)));
    root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  };
  let drag, ghost, home, kb, kbh, sx, sy, ox, oy, pid, dragged, moving;
  const putBack = el => home[0].insertBefore(el, home[1] && home[1].parentNode === home[0] ? home[1] : null);

  root.addEventListener('pointerdown', e => {
    const item = e.target.closest('[data-dd-item]'), handle = e.target.closest('[data-dd-handle]');
    if (!item || e.button > 0 || kb) return;
    if (!handle && (e.pointerType !== 'mouse' || 'ddHandleOnly' in root.dataset || e.target.closest('a,button,input,select,textarea,label'))) return;
    e.preventDefault(); drag = item; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ghost = null;
  });
  root.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== pid) return;
    if (!ghost) { if (Math.hypot(e.clientX - sx, e.clientY - sy) < 6) return; start(); root.setPointerCapture(pid); }
    ghost.style.translate = `${e.clientX - ox}px ${e.clientY - oy}px`;
    if (e.clientY < 40) scrollBy(0, -12); else if (e.clientY > innerHeight - 40) scrollBy(0, 12);
    const hit = document.elementFromPoint(e.clientX, e.clientY), sel = `[data-dd-list="${home[0].dataset.ddList}"]`;
    const list = hit && (hit.closest(sel) || hit.closest('[data-dd-zone]')?.querySelector(sel)); // a [data-dd-zone] wrapper widens the target
    clearOver(list);
    if (!list || !root.contains(list) || drag.contains(list)) return;
    list.setAttribute('data-over', '');
    if (list !== drag.parentElement && full(list)) return;
    const over = items(list).find(c => c !== drag && c.contains(hit));
    if (over) { const r = over.getBoundingClientRect();
      const ref = (horiz ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2) ? over.nextSibling : over;
      if (ref !== drag && ref !== drag.nextSibling) { list.insertBefore(drag, ref); marks(); }
    } else if (list !== drag.parentElement && !drag.contains(hit)) { list.append(drag); marks(); }
  });
  const end = e => {
    if (!drag || e.pointerId !== pid) return;
    if (ghost) {
      ghost.remove(); drag.removeAttribute('data-dragging'); root.removeAttribute('data-dd-active'); document.body.style.cursor = ''; clearOver();
      if (e.type === 'pointercancel') putBack(drag); else say(name(drag) + ' moved to ' + pos(drag) + '.');
      changed(); dragged = true; setTimeout(() => { dragged = false; });
    }
    drag = null;
  };
  root.addEventListener('pointerup', end); root.addEventListener('pointercancel', end);
  function start() {
    const r = drag.getBoundingClientRect(); ox = sx - r.left; oy = sy - r.top; home = [drag.parentElement, drag.nextSibling];
    ghost = drag.cloneNode(true); ghost.removeAttribute('data-dd-item'); ghost.setAttribute('data-dd-ghost', ''); ghost.setAttribute('aria-hidden', 'true'); ghost.inert = true;
    ghost.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
    Object.assign(ghost.style, { position: 'fixed', left: 0, top: 0, width: r.width + 'px', height: r.height + 'px', margin: 0, zIndex: 999, pointerEvents: 'none' });
    drag.after(ghost); drag.setAttribute('data-dragging', ''); root.setAttribute('data-dd-active', ''); document.body.style.cursor = 'grabbing';
  }

  // Keyboard and screen reader mode: the handle is a toggle button (aria-pressed) that picks the item up.
  const finish = msg => { kb.removeAttribute('data-grabbed'); kbh.setAttribute('aria-pressed', 'false'); root.removeAttribute('data-dd-active'); clearOver(); kb = null; changed(); say(msg); };
  const move = fn => { moving = true; fn(); kbh.focus(); moving = false; kbh.scrollIntoView({ block: 'nearest' }); clearOver(kb.parentElement); kb.parentElement.setAttribute('data-over', ''); changed(); say(name(kb) + ' moved to ' + pos(kb) + '.'); };
  root.addEventListener('click', e => {
    const h = e.target.closest('[data-dd-handle]'); if (!h || dragged) return;
    const item = h.closest('[data-dd-item]');
    if (kb === item) return finish(name(item) + ' dropped, ' + pos(item) + '.');
    if (kb) finish(name(kb) + ' dropped, ' + pos(kb) + '.');
    kb = item; kbh = h; home = [item.parentElement, item.nextSibling];
    item.setAttribute('data-grabbed', ''); h.setAttribute('aria-pressed', 'true'); root.setAttribute('data-dd-active', ''); item.parentElement.setAttribute('data-over', ''); h.focus();
    say(name(item) + ' picked up, ' + pos(item) + '. Use the arrow keys to move it, Space to drop it, Escape to cancel.');
  });
  root.addEventListener('keydown', e => {
    if (!kb || e.target !== kbh) return;
    if (e.key === 'Escape') { e.preventDefault(); moving = true; putBack(kb); kbh.focus(); moving = false; return finish('Move cancelled. ' + name(kb) + ' returned to ' + pos(kb) + '.'); }
    const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }[e.key]; if (!d) return;
    e.preventDefault();
    const l = kb.parentElement, all = items(l), i = all.indexOf(kb), ls = lists(l), side = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
    if (side && ls.length > 1) {
      const t = ls[ls.indexOf(l) + d];
      if (!t) return say(name(kb) + ' is already in ' + l.getAttribute('aria-label') + ', the ' + (d < 0 ? 'first' : 'last') + ' list.');
      if (full(t)) return say(t.getAttribute('aria-label') + ' is full.');
      return move(() => t.insertBefore(kb, items(t)[i] || null));
    }
    const step = grid && !side ? d * all.filter(x => x.offsetTop === all[0].offsetTop).length : d, j = Math.max(0, Math.min(all.length - 1, i + step));
    if (j === i) return say(name(kb) + ' is already ' + (d < 0 ? 'first' : 'last') + '.');
    move(() => l.insertBefore(kb, j > i ? all[j].nextSibling : all[j]));
  });
  root.addEventListener('focusout', e => { if (kb && !moving && e.target === kbh) finish(name(kb) + ' dropped, ' + pos(kb) + '.'); });
  changed();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-dk-muted uppercase">Layers panel</span>
<section class="rounded-2xl border border-dk-line bg-dk-panel p-3.5 text-dk-ink shadow-[0_24px_48px_-24px_rgba(0,0,0,.6)]" data-dd aria-labelledby="dk-layers-t">
  <div class="mx-1 mt-0.5 mb-3 flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1"><h2 class="text-[.95rem] font-bold tracking-[-.01em]" id="dk-layers-t">Layers · Landing page</h2><p class="text-[.78rem] text-dk-muted" id="dk-hint1">Drag a layer, or focus its handle, press Space and use the arrow keys. The top layer is drawn in front.</p></div>
  <ul class="grid gap-1 *:data-dragging:border-dashed *:data-dragging:border-[#6b78c9] *:data-dragging:bg-dk-accent/8 [&>[data-dragging]>*]:invisible *:data-dd-ghost:-rotate-1 *:data-dd-ghost:border-dk-accent *:data-dd-ghost:bg-[#232838] *:data-dd-ghost:shadow-[0_18px_40px_-10px_rgba(0,0,0,.75),0_0_0_1px_rgba(139,156,255,.3)] motion-reduce:*:data-dd-ghost:rotate-0 *:data-grabbed:border-dk-accent *:data-grabbed:bg-[#232838] *:data-grabbed:shadow-[0_0_0_1px_#8b9cff,0_0_22px_-4px_rgba(139,156,255,.55)]" data-dd-list="layers">
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m10 2 3 3-3 3-3-3zM10 12l3 3-3 3-3-3zM5 7l3 3-3 3-3-3zM15 7l3 3-3 3-3-3z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1 text-[.86rem] font-semibold" data-dd-name>Navigation bar</span><span class="text-[.72rem] text-dk-muted">Component</span><span class="min-w-6 rounded-md bg-dk-line px-1.5 py-px text-center text-[.7rem] font-bold text-dk-muted tabular-nums" data-z>0</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M4 5V3.5h12V5M10 3.5v13M7.5 16.5h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="min-w-0 flex-1 text-[.86rem] font-semibold" data-dd-name>Hero title</span><span class="text-[.72rem] text-dk-muted">Text</span><span class="min-w-6 rounded-md bg-dk-line px-1.5 py-px text-center text-[.7rem] font-bold text-dk-muted tabular-nums" data-z>0</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><rect x="2.5" y="3.5" width="15" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="m3 14 4.5-4.5 3.5 3.5 2-2 4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1 text-[.86rem] font-semibold" data-dd-name>Hero photo</span><span class="text-[.72rem] text-dk-muted">Image</span><span class="min-w-6 rounded-md bg-dk-line px-1.5 py-px text-center text-[.7rem] font-bold text-dk-muted tabular-nums" data-z>0</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m10 2 3 3-3 3-3-3zM10 12l3 3-3 3-3-3zM5 7l3 3-3 3-3-3zM15 7l3 3-3 3-3-3z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1 text-[.86rem] font-semibold" data-dd-name>Sign-up button</span><span class="text-[.72rem] text-dk-muted">Component</span><span class="min-w-6 rounded-md bg-dk-line px-1.5 py-px text-center text-[.7rem] font-bold text-dk-muted tabular-nums" data-z>0</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 2v16M14 2v16M2 6h16M2 14h16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="min-w-0 flex-1 text-[.86rem] font-semibold" data-dd-name>Feature grid</span><span class="text-[.72rem] text-dk-muted">Frame</span><span class="min-w-6 rounded-md bg-dk-line px-1.5 py-px text-center text-[.7rem] font-bold text-dk-muted tabular-nums" data-z>0</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint1"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><rect x="3" y="4" width="14" height="12" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><span class="min-w-0 flex-1 text-[.86rem] font-semibold" data-dd-name>Page background</span><span class="text-[.72rem] text-dk-muted">Rectangle</span><span class="min-w-6 rounded-md bg-dk-line px-1.5 py-px text-center text-[.7rem] font-bold text-dk-muted tabular-nums" data-z>0</span></li>
  </ul>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-dk-muted uppercase">Up next queue</span>
<section class="rounded-2xl border border-dk-line bg-dk-panel p-3.5 text-dk-ink shadow-[0_24px_48px_-24px_rgba(0,0,0,.6)]" data-dd aria-labelledby="dk-q-t">
  <div class="mx-1 mt-0.5 mb-3 flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1"><h2 class="text-[.95rem] font-bold tracking-[-.01em]" id="dk-q-t">Queue</h2><p class="text-[.78rem] text-dk-muted" id="dk-hint2">Drag a song to change what plays next, or focus its handle and use Space and the arrow keys.</p></div>
  <div class="mb-2.5 flex items-center gap-3 rounded-xl bg-linear-to-r from-dk-green/14 to-transparent p-2.5"><span class="grid size-[52px] flex-none place-items-center rounded-lg text-white bg-[linear-gradient(135deg,#f97316,#db2777)]"><svg class="size-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 4v12l10-6z"/></svg></span><span class="grid min-w-0 flex-1 leading-[1.3]"><span class="text-[.68rem] font-extrabold tracking-[.1em] text-dk-green uppercase">Now playing</span><b class="text-[.88rem] font-semibold">Afterglow</b><small class="text-[.76rem] text-dk-muted">Mira Sol</small></span><span class="text-[.76rem] text-dk-muted tabular-nums">2:14 / 3:50</span></div>
  <p class="mx-1 mb-1.5 text-[.7rem] font-extrabold tracking-[.1em] text-dk-muted uppercase">Up next</p>
  <ol class="grid gap-1 *:data-dragging:border-dashed *:data-dragging:border-[#6b78c9] *:data-dragging:bg-dk-accent/8 [&>[data-dragging]>*]:invisible *:data-dd-ghost:-rotate-1 *:data-dd-ghost:border-dk-accent *:data-dd-ghost:bg-[#232838] *:data-dd-ghost:shadow-[0_18px_40px_-10px_rgba(0,0,0,.75),0_0_0_1px_rgba(139,156,255,.3)] motion-reduce:*:data-dd-ghost:rotate-0 *:data-grabbed:border-dk-accent *:data-grabbed:bg-[#232838] *:data-grabbed:shadow-[0_0_0_1px_#8b9cff,0_0_22px_-4px_rgba(139,156,255,.55)]" data-dd-list="queue" aria-label="Up next">
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="size-10 flex-none rounded-lg bg-[linear-gradient(135deg,#22d3ee,#6366f1)]" aria-hidden="true"></span><span class="grid min-w-0 flex-1 leading-[1.3]"><b class="text-[.88rem] font-semibold" data-dd-name>Glass Harbour</b><small class="text-[.76rem] text-dk-muted">Nadia Vale</small></span><span class="text-[.76rem] text-dk-muted tabular-nums">3:42</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="size-10 flex-none rounded-lg bg-[linear-gradient(135deg,#a3e635,#0d9488)]" aria-hidden="true"></span><span class="grid min-w-0 flex-1 leading-[1.3]"><b class="text-[.88rem] font-semibold" data-dd-name>Night Bus</b><small class="text-[.76rem] text-dk-muted">The Lindens</small></span><span class="text-[.76rem] text-dk-muted tabular-nums">4:05</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="size-10 flex-none rounded-lg bg-[linear-gradient(135deg,#fbbf24,#ef4444)]" aria-hidden="true"></span><span class="grid min-w-0 flex-1 leading-[1.3]"><b class="text-[.88rem] font-semibold" data-dd-name>Saltwater</b><small class="text-[.76rem] text-dk-muted">Ora Kim</small></span><span class="text-[.76rem] text-dk-muted tabular-nums">3:18</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="size-10 flex-none rounded-lg bg-[linear-gradient(135deg,#818cf8,#c026d3)]" aria-hidden="true"></span><span class="grid min-w-0 flex-1 leading-[1.3]"><b class="text-[.88rem] font-semibold" data-dd-name>Low Orbit</b><small class="text-[.76rem] text-dk-muted">Felix Ames</small></span><span class="text-[.76rem] text-dk-muted tabular-nums">5:01</span></li>
    <li class="flex cursor-grab items-center gap-2.5 rounded-[10px] border border-transparent bg-dk-row py-1.5 pr-2.5 pl-1 select-none hover:border-dk-line" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-[7px] text-dk-muted hover:bg-dk-line hover:text-dk-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dk-accent" type="button" data-dd-handle aria-describedby="dk-hint2"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h14v2H3zM3 12h14v2H3z"/></svg></button><span class="size-10 flex-none rounded-lg bg-[linear-gradient(135deg,#94a3b8,#334155)]" aria-hidden="true"></span><span class="grid min-w-0 flex-1 leading-[1.3]"><b class="text-[.88rem] font-semibold" data-dd-name>Paper Moon</b><small class="text-[.76rem] text-dk-muted">June and the Tides</small></span><span class="text-[.76rem] text-dk-muted tabular-nums">3:56</span></li>
  </ol>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>
CSS
@theme {
  --color-dk-panel: #151821;
  --color-dk-row: #1b1f2a;
  --color-dk-line: #262b36;
  --color-dk-ink: #e6e9ef;
  --color-dk-muted: #9aa3b5;
  --color-dk-accent: #8b9cff;
  --color-dk-green: #34d399;
}
JavaScript
// Layers: number the stacking order, top layer first, so the z-index a row will get is visible.
document.addEventListener('dd:change', e => {
  const rows = e.target.querySelectorAll('[data-z]');
  rows.forEach((n, i) => { n.textContent = rows.length - i; });
});
// Drag and drop with pointer events (mouse, touch and pen) plus a keyboard mode on every handle:
// Space or Enter picks an item up, the arrow keys move it, Space or Enter drops it, Escape puts it back.
// Every move is announced in the component's aria-live region. Markup contract: [data-dd] root,
// [data-dd-list="group"] lists, [data-dd-item] items with a [data-dd-name] and a [data-dd-handle] button.
// data-dd="grid" or "wrap" compares the pointer horizontally; in a grid, Up and Down jump a whole row.
document.querySelectorAll('[data-dd]').forEach(root => {
  const grid = root.dataset.dd === 'grid', horiz = grid || root.dataset.dd === 'wrap', live = root.querySelector('[data-dd-live]');
  const items = l => [...l.children].filter(c => c.hasAttribute('data-dd-item'));
  const lists = l => [...root.querySelectorAll(`[data-dd-list="${l.dataset.ddList}"]`)];
  const name = el => el.querySelector('[data-dd-name]').textContent.trim();
  const full = l => !!l.dataset.ddMax && items(l).length >= +l.dataset.ddMax;
  const pos = el => { const l = el.parentElement, all = items(l);
    return (lists(l).length > 1 ? l.getAttribute('aria-label') + ', ' : '') + 'position ' + (all.indexOf(el) + 1) + ' of ' + all.length; };
  const say = msg => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };
  const clearOver = keep => root.querySelectorAll('[data-over]').forEach(l => l !== keep && l.removeAttribute('data-over'));
  const marks = () => root.querySelectorAll('[data-dd-list]').forEach(l => { const n = items(l).length, c = root.querySelector(`[data-dd-count="${l.id}"]`);
    l.toggleAttribute('data-empty', !n); l.toggleAttribute('data-full', full(l)); if (c) c.textContent = n; });
  const changed = () => {
    marks();
    root.querySelectorAll('[data-dd-item]').forEach(it => it.querySelector('[data-dd-handle]').setAttribute('aria-label', 'Move ' + name(it)));
    root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  };
  let drag, ghost, home, kb, kbh, sx, sy, ox, oy, pid, dragged, moving;
  const putBack = el => home[0].insertBefore(el, home[1] && home[1].parentNode === home[0] ? home[1] : null);

  root.addEventListener('pointerdown', e => {
    const item = e.target.closest('[data-dd-item]'), handle = e.target.closest('[data-dd-handle]');
    if (!item || e.button > 0 || kb) return;
    if (!handle && (e.pointerType !== 'mouse' || 'ddHandleOnly' in root.dataset || e.target.closest('a,button,input,select,textarea,label'))) return;
    e.preventDefault(); drag = item; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ghost = null;
  });
  root.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== pid) return;
    if (!ghost) { if (Math.hypot(e.clientX - sx, e.clientY - sy) < 6) return; start(); root.setPointerCapture(pid); }
    ghost.style.translate = `${e.clientX - ox}px ${e.clientY - oy}px`;
    if (e.clientY < 40) scrollBy(0, -12); else if (e.clientY > innerHeight - 40) scrollBy(0, 12);
    const hit = document.elementFromPoint(e.clientX, e.clientY), sel = `[data-dd-list="${home[0].dataset.ddList}"]`;
    const list = hit && (hit.closest(sel) || hit.closest('[data-dd-zone]')?.querySelector(sel)); // a [data-dd-zone] wrapper widens the target
    clearOver(list);
    if (!list || !root.contains(list) || drag.contains(list)) return;
    list.setAttribute('data-over', '');
    if (list !== drag.parentElement && full(list)) return;
    const over = items(list).find(c => c !== drag && c.contains(hit));
    if (over) { const r = over.getBoundingClientRect();
      const ref = (horiz ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2) ? over.nextSibling : over;
      if (ref !== drag && ref !== drag.nextSibling) { list.insertBefore(drag, ref); marks(); }
    } else if (list !== drag.parentElement && !drag.contains(hit)) { list.append(drag); marks(); }
  });
  const end = e => {
    if (!drag || e.pointerId !== pid) return;
    if (ghost) {
      ghost.remove(); drag.removeAttribute('data-dragging'); root.removeAttribute('data-dd-active'); document.body.style.cursor = ''; clearOver();
      if (e.type === 'pointercancel') putBack(drag); else say(name(drag) + ' moved to ' + pos(drag) + '.');
      changed(); dragged = true; setTimeout(() => { dragged = false; });
    }
    drag = null;
  };
  root.addEventListener('pointerup', end); root.addEventListener('pointercancel', end);
  function start() {
    const r = drag.getBoundingClientRect(); ox = sx - r.left; oy = sy - r.top; home = [drag.parentElement, drag.nextSibling];
    ghost = drag.cloneNode(true); ghost.removeAttribute('data-dd-item'); ghost.setAttribute('data-dd-ghost', ''); ghost.setAttribute('aria-hidden', 'true'); ghost.inert = true;
    ghost.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
    Object.assign(ghost.style, { position: 'fixed', left: 0, top: 0, width: r.width + 'px', height: r.height + 'px', margin: 0, zIndex: 999, pointerEvents: 'none' });
    drag.after(ghost); drag.setAttribute('data-dragging', ''); root.setAttribute('data-dd-active', ''); document.body.style.cursor = 'grabbing';
  }

  // Keyboard and screen reader mode: the handle is a toggle button (aria-pressed) that picks the item up.
  const finish = msg => { kb.removeAttribute('data-grabbed'); kbh.setAttribute('aria-pressed', 'false'); root.removeAttribute('data-dd-active'); clearOver(); kb = null; changed(); say(msg); };
  const move = fn => { moving = true; fn(); kbh.focus(); moving = false; kbh.scrollIntoView({ block: 'nearest' }); clearOver(kb.parentElement); kb.parentElement.setAttribute('data-over', ''); changed(); say(name(kb) + ' moved to ' + pos(kb) + '.'); };
  root.addEventListener('click', e => {
    const h = e.target.closest('[data-dd-handle]'); if (!h || dragged) return;
    const item = h.closest('[data-dd-item]');
    if (kb === item) return finish(name(item) + ' dropped, ' + pos(item) + '.');
    if (kb) finish(name(kb) + ' dropped, ' + pos(kb) + '.');
    kb = item; kbh = h; home = [item.parentElement, item.nextSibling];
    item.setAttribute('data-grabbed', ''); h.setAttribute('aria-pressed', 'true'); root.setAttribute('data-dd-active', ''); item.parentElement.setAttribute('data-over', ''); h.focus();
    say(name(item) + ' picked up, ' + pos(item) + '. Use the arrow keys to move it, Space to drop it, Escape to cancel.');
  });
  root.addEventListener('keydown', e => {
    if (!kb || e.target !== kbh) return;
    if (e.key === 'Escape') { e.preventDefault(); moving = true; putBack(kb); kbh.focus(); moving = false; return finish('Move cancelled. ' + name(kb) + ' returned to ' + pos(kb) + '.'); }
    const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }[e.key]; if (!d) return;
    e.preventDefault();
    const l = kb.parentElement, all = items(l), i = all.indexOf(kb), ls = lists(l), side = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
    if (side && ls.length > 1) {
      const t = ls[ls.indexOf(l) + d];
      if (!t) return say(name(kb) + ' is already in ' + l.getAttribute('aria-label') + ', the ' + (d < 0 ? 'first' : 'last') + ' list.');
      if (full(t)) return say(t.getAttribute('aria-label') + ' is full.');
      return move(() => t.insertBefore(kb, items(t)[i] || null));
    }
    const step = grid && !side ? d * all.filter(x => x.offsetTop === all[0].offsetTop).length : d, j = Math.max(0, Math.min(all.length - 1, i + step));
    if (j === i) return say(name(kb) + ' is already ' + (d < 0 ? 'first' : 'last') + '.');
    move(() => l.insertBefore(kb, j > i ? all[j].nextSibling : all[j]));
  });
  root.addEventListener('focusout', e => { if (kb && !moving && e.target === kbh) finish(name(kb) + ' dropped, ' + pos(kb) + '.'); });
  changed();
});

About this set

Drag and drop for dark interfaces, where the usual light grey placeholder disappears and focus rings are easy to lose. Layers panel is a design tool sidebar with six layers, each with a type icon for component, text, image, frame or rectangle and a stacking number that is rewritten after every move, so the top layer is always the one drawn in front. Up next queue is a music player queue under a fixed Now playing card, with cover art, artist and length on each song; drag a song to change what plays next. Use them in editors, media apps and dark dashboards. The colours are chosen for contrast on a #151821 panel: text in #e6e9ef, secondary text in #9aa3b5 and an indigo accent, #8b9cff, for the focus ring, the glowing outline of a picked-up item and the dashed placeholder. The moving copy gets a deep shadow and an accent edge so it stays visible on the dark ground. The Bootstrap version runs on data-bs-theme=”dark” with a .list-group, and the keyboard and screen reader behaviour matches every other set.

What’s included

  • Layers panel with live stacking numbers
  • Music queue under a fixed Now playing card
  • Contrast-checked colours on a near-black panel
  • Glowing outline for the picked-up item
  • Bootstrap version on data-bs-theme="dark"

Accessibility

Every handle is a toggle button named Move plus the item name, with aria-pressed and the visible instructions joined through aria-describedby. Space or Enter picks the item up, the arrow keys move it, Space or Enter drops it and Escape puts it back, and each step is announced in an assertive aria-live region, for example "Hero title moved to position 2 of 6.". Handles show a visible focus outline, and the tilt of the dragged copy and any transitions switch off under prefers-reduced-motion.

Customise it

Change --dk-panel, --dk-row, --dk-accent and --dk-muted on .dk. Bootstrap reads the same variables beside its dark theme; Tailwind keeps the dk colours in @theme.