List supporting reordering.
Use the move buttons to reorder — no dragging required.
<div class="srt srt--card" style="--accent:#F5C2E0;--gap:6px;">
<h3 class="srt__heading">Playlist order</h3>
<p class="srt__hint">Use the move buttons to reorder — no dragging required.</p>
<ol class="srt__list">
<li class="srt__row">
<span class="srt__handle" aria-hidden="true">⠿</span>
<span class="srt__text">Morning warm-up</span>
<span class="srt__moves">
<button type="button" class="srt__move" disabled aria-label="Move Morning warm-up up">↑</button>
<button type="button" class="srt__move" aria-label="Move Morning warm-up down">↓</button>
</span>
</li>
<li class="srt__row">
<span class="srt__handle" aria-hidden="true">⠿</span>
<span class="srt__text">Focus block</span>
<span class="srt__moves">
<button type="button" class="srt__move" aria-label="Move Focus block up">↑</button>
<button type="button" class="srt__move" aria-label="Move Focus block down">↓</button>
</span>
</li>
<li class="srt__row">
<span class="srt__handle" aria-hidden="true">⠿</span>
<span class="srt__text">Wind-down</span>
<span class="srt__moves">
<button type="button" class="srt__move" aria-label="Move Wind-down up">↑</button>
<button type="button" class="srt__move" disabled aria-label="Move Wind-down down">↓</button>
</span>
</li>
</ol>
</div>
<style>
.srt { font: 14px/1.4 system-ui, sans-serif; color: #241430; background: #FAF7F2; max-width: 320px; width: 320px; padding: 16px; border-radius: 12px; box-sizing: border-box; }
.srt__heading { margin: 0 0 4px; font-size: 15px; font-weight: 700; }
.srt__hint { margin: 0 0 10px; font-size: 12px; color: #241430a0; }
.srt__list { list-style: none; margin: 0; padding: 0; counter-reset: srt; }
.srt__row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; margin-bottom: var(--gap); border-radius: 8px; }
.srt--card .srt__row { background: #fff; box-shadow: 0 1px 4px rgba(36,20,48,.08); }
.srt--numbered .srt__row { counter-increment: srt; }
.srt--numbered .srt__row::before { content: counter(srt) "."; font-weight: 700; color: var(--accent); }
.srt__handle { color: #24143070; cursor: grab; }
.srt__text { flex: 1; }
.srt__moves { display: flex; gap: 2px; }
.srt__move { width: 24px; height: 24px; border-radius: 6px; border: 1px solid #24143020; background: #fff; cursor: pointer; font-size: 12px; line-height: 1; }
.srt__move:hover:not(:disabled) { background: var(--accent); }
.srt__move:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.srt__move:disabled { opacity: .35; cursor: not-allowed; }
</style><div class="srt-tw srt-tw--card font-sans text-sm text-[#241430] bg-[#FAF7F2] max-w-[320px] w-[320px] p-4 rounded-xl box-border" style="--accent:#F5C2E0">
<h3 class="m-0 mb-1 text-[15px] font-bold">Playlist order</h3>
<p class="m-0 mb-2.5 text-xs text-[#241430a0]">Use the move buttons to reorder — no dragging required.</p>
<ol class="list-none m-0 p-0">
<li class="srt-tw__row flex items-center gap-2 py-2 px-2.5 rounded-lg" style="margin-bottom:6px">
<span aria-hidden="true" class="text-[#24143070] cursor-grab">⠿</span>
<span class="flex-1">Morning warm-up</span>
<span class="flex gap-0.5">
<button type="button" disabled aria-label="Move Morning warm-up up" class="srt-tw__move w-6 h-6 rounded-md border border-[#24143020] bg-white cursor-not-allowed text-xs opacity-35">↑</button>
<button type="button" aria-label="Move Morning warm-up down" class="srt-tw__move w-6 h-6 rounded-md border border-[#24143020] bg-white cursor-pointer text-xs">↓</button>
</span>
</li>
<li class="srt-tw__row flex items-center gap-2 py-2 px-2.5 rounded-lg" style="margin-bottom:6px">
<span aria-hidden="true" class="text-[#24143070] cursor-grab">⠿</span>
<span class="flex-1">Focus block</span>
<span class="flex gap-0.5">
<button type="button" aria-label="Move Focus block up" class="srt-tw__move w-6 h-6 rounded-md border border-[#24143020] bg-white cursor-pointer text-xs">↑</button>
<button type="button" aria-label="Move Focus block down" class="srt-tw__move w-6 h-6 rounded-md border border-[#24143020] bg-white cursor-pointer text-xs">↓</button>
</span>
</li>
<li class="srt-tw__row flex items-center gap-2 py-2 px-2.5 rounded-lg">
<span aria-hidden="true" class="text-[#24143070] cursor-grab">⠿</span>
<span class="flex-1">Wind-down</span>
<span class="flex gap-0.5">
<button type="button" aria-label="Move Wind-down up" class="srt-tw__move w-6 h-6 rounded-md border border-[#24143020] bg-white cursor-pointer text-xs">↑</button>
<button type="button" disabled aria-label="Move Wind-down down" class="srt-tw__move w-6 h-6 rounded-md border border-[#24143020] bg-white cursor-not-allowed text-xs opacity-35">↓</button>
</span>
</li>
</ol>
</div>
<style>
.srt-tw--card .srt-tw__row { background: #fff; box-shadow: 0 1px 4px rgba(36,20,48,.08); }
.srt-tw--numbered .srt-tw__row { position: relative; padding-left: 24px; }
.srt-tw__move:hover:not(:disabled) { background: #F5C2E0; }
.srt-tw__move:focus-visible { outline: 2px solid #F5C2E0; outline-offset: 1px; }
</style>import { useState } from "react";
export function SortableList({
heading = "Playlist order",
accent = "#F5C2E0",
style = "card",
showHandle = true,
gap = 6,
}) {
const [items, setItems] = useState(["Morning warm-up", "Focus block", "Wind-down"]);
const move = (i, dir) => {
const j = i + dir;
if (j < 0 || j >= items.length) return;
const next = [...items];
[next[i], next[j]] = [next[j], next[i]];
setItems(next);
};
const rowStyle = {
display: "flex", alignItems: "center", gap: 8, padding: "8px 10px", marginBottom: gap, borderRadius: 8,
background: style === "card" ? "#fff" : "transparent",
boxShadow: style === "card" ? "0 1px 4px rgba(36,20,48,.08)" : "none",
};
return (
<div style={{ fontFamily: "system-ui, sans-serif", fontSize: 14, color: "#241430", background: "#FAF7F2", maxWidth: 320, width: 320, padding: 16, borderRadius: 12, boxSizing: "border-box" }}>
<h3 style={{ margin: "0 0 4px", fontSize: 15, fontWeight: 700 }}>{heading}</h3>
<p style={{ margin: "0 0 10px", fontSize: 12, color: "#241430a0" }}>Use the move buttons to reorder — no dragging required.</p>
<ol style={{ listStyle: "none", margin: 0, padding: 0 }}>
{items.map((item, i) => (
<li key={item} style={rowStyle}>
{showHandle && <span aria-hidden="true" style={{ color: "#24143070", cursor: "grab" }}>⠿</span>}
{style === "numbered" && <span style={{ fontWeight: 700, color: accent }}>{i + 1}.</span>}
<span style={{ flex: 1 }}>{item}</span>
<span style={{ display: "flex", gap: 2 }}>
<button type="button" disabled={i === 0} aria-label={`Move ${item} up`} onClick={() => move(i, -1)} style={{ width: 24, height: 24, borderRadius: 6, border: "1px solid #24143020", background: "#fff", cursor: i === 0 ? "not-allowed" : "pointer", opacity: i === 0 ? 0.35 : 1, fontSize: 12 }}>↑</button>
<button type="button" disabled={i === items.length - 1} aria-label={`Move ${item} down`} onClick={() => move(i, 1)} style={{ width: 24, height: 24, borderRadius: 6, border: "1px solid #24143020", background: "#fff", cursor: i === items.length - 1 ? "not-allowed" : "pointer", opacity: i === items.length - 1 ? 0.35 : 1, fontSize: 12 }}>↓</button>
</span>
</li>
))}
</ol>
</div>
);
}