audiocn

Track List

Component

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;

Dependencies

More by audiocn

View all
  • Channel Strip

    Channel Strip

    3
  • Volume Control

    Volume Control

  • LCD Display

    LCD Display

  • Audio Mixer

    Audio Mixer

  • Audio Device Select

    Audio Device Select