About
A list of audio or music tracks with active and currently-playing states, arrow-key keyboard navigation, and slots for artwork, index, duration and per-row actions.
Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Track List to your project and fits it to your theme.
Code
How Track List is used: the demo's code. The component's own source comes with a free account.
Usage.tsx
"use client";
import {
TrackList,
TrackListItem,
TrackListItemActions,
TrackListItemContent,
TrackListItemDescription,
TrackListItemDuration,
TrackListItemIndex,
TrackListItemTitle,
} from "@/components/ui/track-list";
import { useState } from "react";
const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_HOUR = 3600;
/** Formats seconds as `m:ss`, or `h:mm:ss` from one hour. Synthetic demo data only. */
function formatTime(seconds: number): string {
const total = Number.isFinite(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
const hours = Math.floor(total / SECONDS_PER_HOUR);
const minutes = Math.floor((total % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
const secs = total % SECONDS_PER_MINUTE;
const pad = (value: number) => String(value).padStart(2, "0");
if (hours > 0) {
return `${hours}:${pad(minutes)}:${pad(secs)}`;
}
return `${minutes}:${pad(secs)}`;
}
// Fictional placeholder tracks for demo purposes only, no real audio or artists.
const tracks = [
{
artist: "Demo Artist",
duration: 19.2,
id: "night-drive",
title: "Night Drive",
},
{ artist: "Demo Artist", duration: 24, id: "low-tide", title: "Low Tide" },
{ artist: "Demo Artist", duration: 13.7, id: "arcade", title: "Arcade" },
{
artist: "Not available",
duration: 201,
id: "locked",
title: "Unreleased",
},
];
function TrackListDemo() {
const [current, setCurrent] = useState("low-tide");
const [playing, setPlaying] = useState(true);
return (
<TrackList className="max-w-md" variant="outline">
{tracks.map((track, index) => (
<TrackListItem
active={track.id === current}
disabled={track.id === "locked"}
key={track.id}
onSelect={() => {
setPlaying(track.id === current ? !playing : true);
setCurrent(track.id);
}}
playing={track.id === current && playing}
>
<TrackListItemIndex>{index + 1}</TrackListItemIndex>
<TrackListItemContent>
<TrackListItemTitle>{track.title}</TrackListItemTitle>
<TrackListItemDescription>{track.artist}</TrackListItemDescription>
</TrackListItemContent>
<TrackListItemDuration>
{formatTime(track.duration)}
</TrackListItemDuration>
<TrackListItemActions>
<button
aria-label={`More options for ${track.title}`}
className="inline-flex size-6 shrink-0 items-center justify-center rounded-2xl text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
type="button"
>
<svg
aria-hidden="true"
className="size-3"
fill="none"
viewBox="0 0 24 24"
>
<circle cx="5" cy="12" fill="currentColor" r="1.5" />
<circle cx="12" cy="12" fill="currentColor" r="1.5" />
<circle cx="19" cy="12" fill="currentColor" r="1.5" />
</svg>
</button>
</TrackListItemActions>
</TrackListItem>
))}
</TrackList>
);
}
export default TrackListDemo;





