Skip to main content
RapidDev - Software Development Agency
V0 TemplatesUI ComponentIntermediate to customize

Spotify UI V0 Template: Music Player Layout for Next.js

The Spotify UI v0 template delivers a full three-column dark-mode media player layout built on Next.js, Tailwind CSS, shadcn/ui, Framer Motion, and Lucide React. It includes a sticky NowPlayingBar with playback controls, a scrollable SidebarLibrary, and an AlbumGrid with hover overlays. Fork it in under five minutes and use the included prompt pack to wire real audio, connect the Spotify API, or save liked tracks to Supabase.

UI ComponentIntermediate~10 minutes

Best for

Founders building music, podcast, or media player UIs who want a Spotify-inspired dark-mode layout with playback controls

Stack

Next.jsTypeScriptTailwind CSSshadcn/uiFramer MotionLucide React

A ready-made Spotify UI UI you can fork, run, and customize with the prompt pack below.

What's actually inside

The honest engineer's breakdown — what the Spotify UItemplate does, how it's wired, and where it's opinionated.

The Spotify UI template is a high-fidelity clone of Spotify's desktop web layout — not just a color scheme, but a three-column CSS Grid that mimics the real sidebar-content-queue arrangement. The SpotifyLayout component pins the NowPlayingBar at the bottom using fixed positioning, keeps the SidebarLibrary scrollable on the left, and gives the main area an AlbumGrid of CardAlbum components with hover-reveal play overlays animated by Framer Motion. The usePlayer hook simulates playback state (play/pause, skip, progress) using setInterval and useState, so the UI feels live even without real audio.

The component library choices are deliberate: Lucide React supplies every icon (SkipBack, Play, SkipForward, Shuffle, Repeat, Volume2) in a consistent stroke style, while shadcn/ui Slider handles both the ProgressSlider and VolumeSlider — meaning you get accessible range inputs with the correct ARIA roles out of the box. Framer Motion is used sparingly but effectively: the NowPlayingBar mounts with a slide-up animation, and each AlbumGrid card's hover overlay fades from opacity-0 to opacity-100.

The honest caveat: this template ships with completely hardcoded track and album data. The usePlayer timer is fake — it increments a counter but plays no audio. The three-column layout does not collapse gracefully on mobile; the CSS Grid uses fixed pixel widths that overflow small screens. These are intentional trade-offs for a UI demo, and the prompt pack below addresses all three gaps with copy-paste prompts.

Key UI components

SpotifyLayout

Three-column CSS Grid wrapper with fixed sidebar widths and sticky NowPlayingBar

SidebarLibrary

Scrollable left panel showing playlist/album list with cover art and search filter

AlbumGrid

Responsive grid of CardAlbum components with Framer Motion hover play overlay

NowPlayingBar

Sticky bottom bar with AlbumArt thumbnail, TrackInfo, PlaybackControls, ProgressSlider, and VolumeSlider

PlaybackControls

Five Lucide icon buttons (SkipBack, Play/Pause, SkipForward, Shuffle, Repeat) wired to usePlayer hook

ProgressSlider

shadcn/ui Slider showing current playback time and total duration, driven by usePlayer timer

usePlayer

Central hook managing isPlaying state, current track, and fake setInterval progress counter

Libraries it leans on

Framer Motion

NowPlayingBar mount animation and AlbumGrid card hover overlay transitions

Lucide React

All playback and UI icons — SkipBack, Play, Pause, SkipForward, Shuffle, Repeat, Volume2

shadcn/ui

Slider (ProgressSlider and VolumeSlider), ScrollArea for SidebarLibrary, Avatar for track art

Tailwind CSS

Dark-mode color system (bg-zinc-900/800), CSS Grid layout, fixed and sticky positioning

Fork it and get it running

Forking and running the Spotify UI template takes about ten minutes — the bulk of the time is replacing the hardcoded track data with your own content. No account other than V0 is needed to get a live preview.

1

Fork the template from the V0 community page

Open https://v0.dev/chat/community/C79ouU937xz in your browser. You will see the Spotify UI template with a live Vercel Sandbox preview on the right side. Click the blue Fork button in the top-right corner of the page — V0 will copy the entire project into your account and open it in the full V0 editor with both the chat panel and code editor available.

Tip: You must be signed in to a V0 account to fork. The free plan is sufficient.

You should see: The project opens in your V0 editor with the SpotifyLayout visible in the Vercel Sandbox preview on the right.

2

Verify the layout and playback controls in preview

In the Vercel Sandbox preview pane, check that the three-column SpotifyLayout renders correctly: SidebarLibrary on the left, AlbumGrid in the center, and the NowPlayingBar pinned at the bottom. Click the Play/Pause button in PlaybackControls and watch the ProgressSlider move — this confirms the usePlayer hook is running. If the layout overflows horizontally, try maximizing your browser window before making any edits.

Tip: The preview runs in a Vercel Sandbox (cloud environment) so Next.js SSR is fully active — any window-related errors will appear here, not just in your browser console.

You should see: Three-column layout renders, NowPlayingBar is visible at the bottom, and clicking Play starts the fake ProgressSlider timer.

3

Replace hardcoded track and album data

Click the Code tab in the V0 editor to open the file browser. Look for a file containing a tracks array or albumData constant — this is where all the hardcoded playlist content lives. Replace the placeholder track objects with your own data (title, artist, cover image URL, duration). This is a direct code edit in V0's built-in editor, so it costs no credits. Save the file and the preview will hot-reload automatically.

Tip: For cover images, use publicly accessible URLs (Unsplash, your own CDN, or Supabase Storage) — relative paths will not work in preview.

You should see: AlbumGrid and SidebarLibrary show your custom tracks and album art after hot reload.

4

Adjust the dark color palette using Design Mode

Press Option+D (Mac) to enter V0's Design Mode. Click on the SpotifyLayout background — it defaults to bg-zinc-900. Click on the SidebarLibrary panel — usually bg-zinc-800. You can swap these to your brand's dark palette without spending any prompt credits. Click on NowPlayingBar to adjust its border and background as well. Exit Design Mode with Escape when done.

Tip: Spotify's palette is intentionally zinc-900 / zinc-800 with green-500 accents. If you're building a podcast app, consider slate or neutral instead.

You should see: The SpotifyLayout renders with your brand colors in the preview, with no code credits consumed.

5

Publish to a live Vercel URL

Click Share in the V0 top navigation, then select the Publish tab. Click 'Publish to Production' — V0 builds and deploys in 30–60 seconds and gives you a live Vercel subdomain URL (e.g., spotify-ui-abc123.vercel.app). Share this URL to preview on any device or with stakeholders. To connect a custom domain later, open Vercel Dashboard at vercel.com/dashboard, select the project, go to Settings → Domains → Add Domain.

Tip: Use the Git panel to connect GitHub — V0 auto-creates branch v0/main-abc123 and opens a PR so you can merge the template into your existing codebase.

You should see: A live URL is generated and the Spotify UI renders correctly on the deployed Vercel environment.

The prompt pack

Copy-paste these straight into v0's chat to customize the Spotify UItemplate. Each one names this template's own components — no generic filler.

1

Wire play button to real audio playback

Replaces the simulated progress counter with real HTML5 audio playback, making PlaybackControls and ProgressSlider respond to actual audio files.

Medium
Paste into v0 chat
Replace the fake setInterval timer in the usePlayer hook with the Web Audio API's HTMLAudioElement. Add an audioRef = useRef<HTMLAudioElement>(null) inside usePlayer and render an <audio ref={audioRef} src={currentTrack.audioUrl} /> element in NowPlayingBar. Connect the PlaybackControls play button to audioRef.current.play() and the pause button to audioRef.current.pause(). Sync the ProgressSlider's value with the audio element's currentTime by adding a timeupdate event listener that updates a state variable. Add a cleanup in useEffect to remove the timeupdate listener on unmount. Initialize all time display values with '0:00' to avoid React hydration mismatch on the ProgressSlider.
2

Swap dark theme to light mode with a toggle

Adds a light-mode variant to the Spotify UI so it can serve podcast or audiobook apps that don't need the dark aesthetic.

Quick win
Paste into v0 chat
Add a light/dark theme toggle button to the SpotifyLayout header area. When light mode is active, update the SpotifyLayout background to bg-white, the SidebarLibrary panel to bg-gray-50, and the NowPlayingBar to bg-gray-100 border-t border-gray-200. Change all text colors from text-white and text-zinc-400 to text-gray-900 and text-gray-500. Update each AlbumGrid CardAlbum to use bg-gray-100 hover:bg-gray-200. Use Tailwind's dark: prefix classes and toggle a class attribute on the html element to switch between modes.
3

Add a search bar that filters the AlbumGrid

Adds client-side filtering to the AlbumGrid so users can quickly find tracks or albums by name without any API calls.

Quick win
Paste into v0 chat
Add a text input in the main content area's header row — above the AlbumGrid — with a placeholder 'Search tracks and albums…'. Wire it to a searchQuery state variable. Filter the albumData array in real-time using a case-insensitive String.includes() check on both the track title and artist name fields. When the filtered array is empty, render a centered message 'No results for "{searchQuery}"' in place of the AlbumGrid. Keep the SidebarLibrary search input filtering playlists independently from this main search.
4

Replace hardcoded tracks with Spotify API data

Replaces the static track list with live data from a real Spotify playlist, making the AlbumGrid and SidebarLibrary reflect actual content.

Advanced
Paste into v0 chat
Create a GET /api/spotify/tracks route handler in Next.js App Router that calls the Spotify Web API's Get Playlist Tracks endpoint. Use a SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET stored as server-side environment variables (no NEXT_PUBLIC_ prefix) to implement the Client Credentials OAuth flow — POST to https://accounts.spotify.com/api/token and cache the access token in memory with its expiry. Return a normalized array of { id, title, artist, albumArt, durationMs } objects. In the AlbumGrid component, fetch this route on mount using useEffect and replace the hardcoded albumData constant with the API response. Show shadcn/ui Skeleton loaders in the AlbumGrid card positions while the data loads.
5

Save liked tracks to Supabase with Clerk auth

Adds a persisted like system to the AlbumGrid cards, storing user preferences in Supabase with Clerk authentication gating the write.

Advanced
Paste into v0 chat
Add a Lucide Heart icon button to each AlbumGrid CardAlbum. When clicked, call a Server Action that inserts { user_id, track_id, track_title, artist, cover_url } into a Supabase liked_tracks table. On mount, fetch the user's liked track IDs from Supabase using supabase.from('liked_tracks').select('track_id').eq('user_id', userId) and store them in a Set for O(1) lookups. Render a filled HeartFilled icon for tracks in the Set and an outline Heart for the rest. Use NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY from environment variables. Use Clerk's auth() server function to get userId — redirect to /sign-in if the user is not authenticated.

Gotchas when you extend it

The failures people actually hit when they push this template past its defaults — and the exact fix for each.

window is not defined (HTMLAudioElement or Web Audio API called during SSR)

Why: If you wire the PlaybackControls play button to new Audio() or access audioRef directly in the component body, Next.js App Router SSR throws because the window object doesn't exist on the server — NowPlayingBar renders server-side on first load.

Fix: Wrap all HTMLAudioElement creation inside a useEffect, or add 'use client' to the usePlayer file and import NowPlayingBar dynamically using next/dynamic with { ssr: false } so it never renders on the server.

Fix prompt — paste into v0
Fix the window is not defined error in the usePlayer hook — move all HTMLAudioElement and Web Audio API calls inside useEffect, or use next/dynamic with ssr: false to import the NowPlayingBar component and prevent server-side rendering.
Three-column SpotifyLayout breaks on mobile (sidebars collapse incorrectly)

Why: The CSS Grid uses fixed pixel widths for SidebarLibrary and the right panel — on screens below 768px the grid overflows the viewport instead of stacking, and the NowPlayingBar may be partially hidden by the mobile browser chrome.

Fix: Add responsive grid classes: grid-cols-1 on mobile, grid-cols-[220px_1fr] on md (hide right panel), and grid-cols-[220px_1fr_280px] on lg. Ensure NowPlayingBar uses fixed bottom-0 left-0 w-full so it always stays visible.

Fix prompt — paste into v0
Make the SpotifyLayout fully responsive — collapse to single column on mobile, two-column on md (hide right queue panel), and three-column on lg. The NowPlayingBar must remain sticky at the bottom on all breakpoints with fixed bottom-0 left-0 w-full.
ProgressSlider causes React hydration mismatch

Why: If the ProgressSlider or time display strings (e.g., '0:34 / 3:22') are computed from the usePlayer state during SSR, Next.js generates markup with '0:00' on the server but a different value on the client — React logs a hydration mismatch warning and may flash.

Fix: Initialize all time display values with '0:00' as a constant and only update them client-side inside useEffect after the audio element or fake timer has loaded. Never derive the displayed time from state during the initial render path.

Fix prompt — paste into v0
Fix the React hydration mismatch on the ProgressSlider time display — initialize currentTime and totalTime display strings to '0:00' as static defaults and only update them inside useEffect after mount to prevent SSR/client discrepancy.
Fake setInterval in usePlayer leaks on unmount

Why: If usePlayer starts a setInterval to simulate progress but the useEffect cleanup does not call clearInterval, the interval continues running after the component unmounts — causing 'Can't perform a React state update on an unmounted component' warnings and memory leaks.

Fix: Return () => clearInterval(intervalRef.current) from the useEffect that starts the interval. Store the interval ID in a useRef (not useState) so the cleanup function always has access to the latest ID.

Fix prompt — paste into v0
Fix the memory leak in the usePlayer hook — store the setInterval return value in a useRef, and call clearInterval(intervalRef.current) in the useEffect cleanup function that runs when the component unmounts or when isPlaying changes to false.

Template vs. custom — the honest call

A forked template gets you far, fast. Here's where it holds up, and where you'll outgrow it.

The template is enough when

  • You are building a music or podcast app MVP and need the full three-column UI shell in a day
  • You want a convincing dark-mode NowPlayingBar and AlbumGrid without writing CSS Grid from scratch
  • Your tracks are static, fetched from a single API endpoint, or stored in Supabase
  • You are demoing to investors or users and need something that looks production-ready immediately

Go custom when

  • You need DRM-protected audio playback — the Web Audio API in this template does not support encrypted streams
  • You need real-time collaborative listening rooms — that requires WebSocket infrastructure beyond what this template provides
  • You need a mobile app — this is a web-only Next.js layout; React Native would be the right choice for iOS and Android
  • You need the AlbumGrid to render server-side with full SSR-per-track for SEO (e.g., a public music catalog)

If you need the Spotify UI wired to real audio storage (R2 or S3), user playlists in Supabase, and a Stripe subscription paywall, RapidDev can build that full-stack layer on top of this V0 prototype.

Frequently asked questions

Is the Spotify UI v0 template free to use?

Yes. The template is published in the V0 community and is free to fork. Forking uses one of your V0 credits (free plan includes monthly credits). The underlying code — Next.js, Tailwind, Framer Motion, Lucide React, shadcn/ui — is all MIT-licensed.

Can I use this template commercially?

Yes. The template code is yours once forked — there is no V0 license restriction on commercial use. Note that the Spotify name, logo, and green brand color are Spotify's intellectual property; do not use them in a production product. Use your own branding when building on top of this layout.

Why does my forked Spotify UI break in V0 preview after I add audio?

The most common cause is calling HTMLAudioElement or new Audio() outside of a useEffect — Next.js App Router runs components server-side during preview, and window is not available on the server. Wrap all Web Audio API calls inside useEffect, or add 'use client' to the NowPlayingBar or usePlayer file. If the error reads 'window is not defined', that is the exact fix.

Does this template play real audio out of the box?

No. The usePlayer hook uses a fake setInterval to simulate playback progress — no audio plays by default. To add real audio, use the 'Wire play button to real audio playback' prompt in the prompt pack above, which wires the PlaybackControls to HTMLAudioElement with a src pointing to your audio file URLs.

How do I connect this template to the real Spotify API?

Use the advanced prompt in the pack above: it creates a /api/spotify/tracks Next.js Route Handler that implements the Spotify Client Credentials OAuth flow using SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET (server-side env vars). Add those variables in the V0 Vars panel before prompting so V0 can reference them in the generated code.

The AlbumGrid doesn't show on mobile — how do I fix the layout?

The three-column CSS Grid uses fixed pixel widths that overflow small screens. Use the gotcha fix prompt above to add responsive Tailwind classes: grid-cols-1 on mobile, grid-cols-[220px_1fr] on md, and grid-cols-[220px_1fr_280px] on lg. The NowPlayingBar should use fixed bottom-0 left-0 w-full so it always stays visible regardless of column count.

Can RapidDev customize this template for my music or podcast app?

Yes. RapidDev specializes in extending V0 prototypes like this one into production apps — wiring real audio storage, building user playlist systems in Supabase, or adding Stripe subscription gating. Reach out at rapidevelopers.com for a scoped engagement.

How do I export the forked template to my own GitHub repo?

In V0, open the Git panel (left sidebar) and click 'Connect GitHub'. Authorize V0, select your organization or personal account, and choose a repository name. V0 automatically creates a branch named v0/main-{hash} and commits the full project. Open the pull request on GitHub and merge it to bring the Spotify UI template into your existing codebase.

Outgrowing the template?

RapidDev turns v0 prototypes into production apps — real auth, database, and payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.