A Spotify-like lyrics player that syncs audio with word-by-word highlighting. Built with TanStack Start, React 19, and Framer Motion.
The included demo uses "Stay Woke (Playwright Edition)" - an AI-generated song about software testing created with Suno.
- Word-by-word highlighting - Each word lights up precisely when it's sung
- Click to seek - Click any word to jump to that point in the song
- Smooth animations - Framer Motion provides buttery-smooth transitions
- Auto-scroll - Lyrics automatically scroll to keep the current line centered
- Section gaps - Visual spacing between verse/chorus sections based on audio timing
- Responsive design - Optimized for mobile, tablet, and desktop
- Dark theme - Beautiful dark UI with gradient backgrounds
The lyrics are stored in public/song/lyrics.json with word-level timing:
{
"words": [
{ "text": "Too", "start": 23.9, "end": 24.2, "type": "word" },
{ "text": " ", "start": 24.2, "end": 24.24, "type": "spacing" },
{ "text": "late,", "start": 24.24, "end": 26.12, "type": "word" },
...
]
}Each word has:
text- The word/spacing contentstart/end- Timestamps in secondstype- Either"word","spacing", or"audio_event"(filtered out)
The parser converts raw word data into lines:
- Line splitting - Words are grouped into lines based on sentence-ending punctuation (
.,!,?) - Gap detection - Calculates silence duration between lines to add visual spacing
- Filtering - Removes audio events like
[singing],[music]
Custom hook managing audio playback:
- Play/pause/toggle controls
- Seek to specific timestamps
- Real-time
currentTimeupdates - Duration and loading state
The main visualization component:
- Framer Motion animations - Uses
scaletransforms (not font-size) for smooth focus effects - Distance-based opacity - Lines further from current fade out gradually
- Auto-scroll -
scrollIntoViewkeeps current line centered - Click handlers - Each word is clickable for seeking
Play button and progress slider with:
- Shadcn UI Slider component
- Time display (current / duration)
- Responsive sizing
- Framework: TanStack Start (React meta-framework)
- React: 19.x with React Compiler
- Animations: Framer Motion (
motionpackage) - Styling: Tailwind CSS v4
- UI Components: Shadcn UI
- Build Tool: Vite 7.x
- Language: TypeScript (strict mode)
- Linting: Biome
- Bun (recommended) or Node.js 20+
- bun, npm, or yarn
# Clone the repository
git clone https://github.com/crafter-station/synced-lyrics-player.git
cd synced-lyrics-player
# Install dependencies
bun install
# Start development server
bun devOpen http://localhost:3000 in your browser.
- Replace
public/song/audio.mp3with your audio file - Generate word-level timing data (see below) and save to
public/song/lyrics.json - Update the song title in
src/routes/index.tsx
You can use services like:
- Whisper - OpenAI's speech recognition
- AssemblyAI - API with word-level timestamps
- Suno - If generating AI music, they provide lyrics timing
The JSON structure must match:
interface LyricWord {
text: string
start: number // seconds
end: number // seconds
type: "word" | "spacing" | "audio_event"
}src/
├── components/
│ ├── AudioControls.tsx # Play button + progress slider
│ ├── LyricsDisplay.tsx # Main lyrics visualization
│ └── ui/ # Shadcn UI components
├── hooks/
│ └── useAudioPlayer.ts # Audio playback hook
├── lib/
│ ├── lyrics.ts # Lyrics parsing & utilities
│ └── utils.ts # Utility functions
├── routes/
│ ├── __root.tsx # Root layout
│ └── index.tsx # Main lyrics player page
└── styles.css # Global Tailwind styles
public/
└── song/
├── audio.mp3 # The song audio
└── lyrics.json # Word-level timing data
bun dev # Start dev server on port 3000
bun build # Build for production
bun preview # Preview production build
bun check # Run Biome lint + format checks
bun test # Run Vitest testsMIT