Tape Deck
A 3D shelf of cassette tapes. Hover lifts one, click pulls it out and plays a little beat.
Read how it's builtPreview
Click a tape to open it
Night Bus Home
Lowercase Lions
00:00
01 / 09
Setup
Install with the shadcn CLI
One command adds every file, the npm packages and any Kinetik components it builds on. Needs a components.json; the CLI offers to create one if your project has none.
Or install manually
- 1
Create a
components/ui/tape-deck/folder and copy every file from the Code tab into it. Each file does one job, so you can read or change a piece without the rest. - 2
Import and render:
Exampleimport { TapeDeck, TapeCover } from "@/components/ui/tape-deck" <TapeDeck tapes={[ { id: "one", title: "Night Bus", artist: "Lowercase Lions", cover: <TapeCover background="#2fc0c9" text="NIGHT BUS" /> }, ]} />
No packages to install: it is one file with its own styles, plain React and the Web Audio API.
Usage
Give it a list of tapes. A cover can be the built-in TapeCover, an <img>, or any JSX.
Without a src, each tape plays a small generated beat at its bpm.
import { TapeDeck, TapeCover, type TapeItem } from "@/components/ui/tape-deck"
const tapes: TapeItem[] = [
{
id: "imgnest",
title: "ImgNest",
artist: "Next.js · PostgreSQL",
cover: <TapeCover background="#2fc0c9" color="#0b2e33" text={"IMG\nNEST"} side="A" />,
},
{
id: "craftui",
title: "CraftUI",
artist: "React component library",
cover: <img src="/covers/craftui.png" alt="" />,
src: "/audio/craftui.mp3",
},
]
export function Shelf() {
return <TapeDeck tapes={tapes} className="h-[600px]" />
}
To restyle it, override --fg, --muted, --line and --bg on the deck. To keep music playing after a
tape closes, create a player with useTapePlayer outside and pass it as player.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
tapes | TapeItem[] | — | The tapes on the shelf, in order. |
className | string | — | Size and theme overrides. Default height is clamp(520px, 80vh, 820px). |
player | TapePlayer | — | Outside player. Without one the deck plays tapes itself. |
defaultSelected | number | — | Index of a tape to open on mount. |
| TapeItem | Type | Notes |
|---|---|---|
id | string | Unique. Also varies the generated beat. |
title | string | First line under the open tape. |
artist | string | Second line. |
cover | ReactNode | Fills the label: TapeCover, an img or your own markup. |
src | string? | Audio file. Without it a small beat plays. |
bpm | number? | Tempo of the beat and reel speed. Defaults to 100. |
Accessibility
- Every tape is a focusable button; Enter or Space opens it
- ← → move along the shelf, or between tapes once one is open; Space plays or pauses; Escape closes
- Vertical page scroll is left alone: only horizontal scroll or drag moves the shelf
- With reduced motion on, tapes jump into place and the reels stop spinning