alifeinbinary/tailwind-react-music-player

A beautiful HTML5 responsive music player component for react and styled with Tailwind

TypeScript

0

658 commits

updated Oct 13, 2026

See the code

See what people are saying

SourceMessageScoreDate

I created a modern Music Player, styled with TailwindCSS (r/reactjs)

I have been using the [react-music-player from lijinke666](https://github.com/lijinke666/react-music-player) on my portfolio website since React 16 but it has become abandonware that uses antiquated web technologies, so I took it upon myself to create a modernised version of it, complete with…

0

Oct 13, 2026

README

tailwind-react-music-player

An accessible, themeable HTML5 music player component for React, styled with Tailwind CSS — with a headless useMusicPlayer hook if you want to bring your own UI.

npm version CI bundle size license TypeScript

demo

Live demo — try the player in your browser before installing.

Features

  • Full player UI: mini controller, fixed bottom panel, mobile layout, playlist drawer, floating lyric overlay
  • Headless useMusicPlayer hook — same engine, your own markup
  • Accessible: labelled controls, keyboard shortcuts, focus management, reduced-motion support
  • Themeable: dark / light / auto themes, runtime --trmp-* CSS custom properties, per-slot icon overrides
  • Playlist management: drag-to-sort (SortableJS), remove, reorder, list updates that preserve the current track
  • Lyrics: LRC parsing and sync (parseLrc is exported if you roll your own)
  • Volume fade in/out, perceptual volume mapping, persistence to localStorage
  • Media Session API integration (lock-screen / hardware-key controls)
  • Typed end-to-end; the public API surface is src/types.ts

Requirements

  • React >= 19 (MusicPlayer takes ref as a plain prop — a React 19 feature)
  • ESM-only — no CJS or UMD build; CJS consumers must import() or go through a bundler
  • Node >= 22.19 for the build tooling (the published bundle has no runtime Node dependency)

Install

yarn add tailwind-react-music-player
# or
npm install tailwind-react-music-player
import MusicPlayer from 'tailwind-react-music-player'
import 'tailwind-react-music-player/styles.css' // required — styles are a separate entry point

Quick start

import MusicPlayer from 'tailwind-react-music-player'
import 'tailwind-react-music-player/styles.css'

const audioLists = [
  {
    name: 'Despacito',
    singer: 'Luis Fonsi',
    cover: 'https://example.com/cover.jpg',
    musicSrc: 'https://example.com/despacito.mp3',
    // musicSrc can also resolve lazily:
    // musicSrc: () => fetch('/api/sign').then((r) => r.text()),
    lyric: '[00:00.00]Line one of the lyric',
  },
]

export default function App() {
  return <MusicPlayer audioLists={audioLists} />
}

Props

MusicPlayerProps = UseMusicPlayerOptions & MusicPlayerUIProps — the options below are also accepted directly by useMusicPlayer. src/types.ts is the source of truth.

Playlist, playback and state

Most state can be controlled (value + onChange) or uncontrolled (default*).

PropTypeDefaultDescription
audioListsreadonly AudioTrack[]—Required. The playlist.
playIndexnumber—Controlled index of the playing track
defaultPlayIndexnumber0Initial index (uncontrolled)
onPlayIndexChange(index: number) => void—Fires when the index changes
playModePlayMode—Controlled play mode
defaultPlayModePlayMode'order''order' | 'orderLoop' | 'singleLoop' | 'shufflePlay'
onPlayModeChange(mode: PlayMode) => void—Fires when the play mode changes
themeTheme—Controlled theme
defaultThemeTheme'dark''dark' | 'light' | 'auto' (auto follows prefers-color-scheme)
onThemeChange(theme: Theme) => void—Fires when the theme changes
modePlayerMode—Controlled layout mode
defaultModePlayerMode'mini'¹'mini' | 'full'
onModeChange(mode: PlayerMode) => void—Fires when the mode changes
volumenumber—Controlled volume, slider scale 0..1 (mapped to gain internally)
defaultVolumenumber1Initial volume (uncontrolled)
onVolumeChange(volume: number) => void—Fires when the volume changes
autoPlaybooleantruePlay the first track on load (subject to browser gesture policy)
preload'auto' | 'metadata' | 'none''none'preload attribute on the <audio> element
crossOrigin'anonymous' | 'use-credentials'—crossOrigin attribute on the <audio> element
skipOnErrorbooleantrueSkip to the next track on load/playback errors
restartCurrentOnPrevbooleanfalsePrev restarts the track when past the restart threshold
preserveCurrentOnUpdatebooleanfalseKeep the playing track across audioLists updates
replaceListOnUpdatebooleanfalseReplace the list on audioLists update instead of merging
autoPlayOnListUpdatebooleanfalseAuto-play when audioLists changes
persistboolean | stringfalsePersist state to localStorage; true uses the default key, a string sets a custom key
volumeFadeVolumeFadeOptions—{ fadeIn, fadeOut } durations in ms (skipped on iOS)
mobileMediaQuerystring | null(max-width: 768px) and (orientation: portrait)Media query that selects the mobile UI; null disables
mediaSessionbooleanfalsePublish Media Session metadata and action handlers
random() => numberMath.randomInjectable random source for shuffle (tests)

¹ <MusicPlayer> defaults defaultMode to 'mini'; bare useMusicPlayer defaults to 'full'.

AudioTrack:

interface AudioTrack {
  name?: React.ReactNode
  singer?: React.ReactNode
  cover?: string
  musicSrc: string | (() => Promise<string>)
  lyric?: string
  duration?: number
}

Callbacks

Every callback receives a unified AudioInfo payload (track, index, tracks, currentTime, duration, volume, muted, paused, ended, readyState, networkState, playMode, currentLyric).

PropSignatureDescription
onPlay(info: AudioInfo) => voidPlayback started
onPause(info: AudioInfo) => voidPlayback paused
onEnded(info: AudioInfo) => voidTrack ended
onError(error: MediaError | Error | null, info: AudioInfo) => voidLoad/playback error
onProgress(info: AudioInfo) => voidtimeupdate progress
onSeeked(info: AudioInfo) => voidSeek completed
onAbort(info: AudioInfo) => voidLoad aborted
onTrackChange(info: AudioInfo) => voidCurrent track changed
onTracksChange(tracks: readonly PlaylistItem[], info: AudioInfo) => voidPlaylist contents changed
onLyricChange(line: string, index: number) => voidActive lyric line changed
onReload(info: AudioInfo) => voidReload requested
onBeforeDownload(info: AudioInfo) => DownloadInfo | Promise<DownloadInfo> | voidReturn/resolve a DownloadInfo to customise the download; return nothing for the default { src, filename }
onDownload(info: AudioInfo, download: DownloadInfo) => voidFires after the download resolves
customDownloader(download: DownloadInfo) => voidReplaces the built-in download behaviour (DownloadInfo = { src, filename?, mimeType? })

UI props

PropTypeDefaultDescription
classNamestring—Extra class on the .trmp-root element
styleReact.CSSProperties—Inline style on the root element
containerHTMLElement | (() => HTMLElement) | nulldocument.bodyPortal target; null renders inline (no portal)
localeLocaleId | Partial<Locale>'en_US'Locale id or partial string overrides — see Locales
iconsPartial<PlayerIcons>defaultIconsPer-slot icon overrides — see Icons
draggablebooleantrueMini controller is draggable
dragBoundsstring | DraggableBounds—Drag constraint passed to react-draggable
defaultPosition{ top?, left?, right?, bottom? }{ right: 30, bottom: 30 }Mini controller initial position
allowModeTogglebooleantrueAllow switching mini ⇄ full
responsivebooleantrueSwitch to the mobile UI when mobileMediaQuery matches
seekablebooleantrueProgress bar is interactive
glassBackgroundbooleanfalseFrosted-glass panel background
hideEmptyCoverbooleanfalseHide the cover when a track has none
showMiniCoverbooleantrueShow the cover in the mini controller
showMiniProgressbooleanfalseCircular progress ring on the mini controller
showBufferedbooleantrueShow the buffered-progress bar
showPlayButtonbooleantrueShow the play/pause button
showReloadbooleantrueShow the reload button
showDownloadbooleantrueShow the download button
showPlayModebooleantrueShow the play-mode toggle
showThemeSwitchbooleantrueShow the light/dark theme switch
showLyricbooleanfalseShow the lyric toggle (and enable the lyric overlay)
showDestroybooleanfalseShow the destroy button
removablebooleantrueTracks can be removed from the playlist
sortableboolean | SortableOptionsfalseDrag-to-sort the playlist; an object is forwarded to SortableJS
keyboardShortcutsbooleanfalseEnable keyboard shortcuts — see Accessibility
playModeTipDurationnumber600How long the play-mode toast stays visible (ms)
extraControlsReact.ReactNode—Custom content rendered into the controls row
lyricClassNamestring—Extra class on the lyric overlay
renderTrackTitle(info: AudioInfo, isMobile: boolean) => ReactNode—Custom title renderer (also used for the <audio> title)
onCoverClick(info: AudioInfo) => void—Cover image clicked
onPlaylistToggle(open: boolean) => void—Playlist drawer opened/closed
onBeforeDestroy(info: AudioInfo) => boolean | void | Promise<boolean | void>—Return/resolve false to cancel destruction
onDestroyed(info: AudioInfo) => void—Player destroyed

Ref handle

MusicPlayer takes a React 19 ref prop exposing MusicPlayerHandle: every MusicPlayerActions method, the live audio element, and destroy().

import { useRef } from 'react'
import MusicPlayer, {
  type MusicPlayerHandle,
} from 'tailwind-react-music-player'

function App() {
  const playerRef = useRef<MusicPlayerHandle>(null)

  return (
    <>
      <MusicPlayer ref={playerRef} audioLists={audioLists} />
      <button onClick={() => playerRef.current?.togglePlay()}>toggle</button>
      <button onClick={() => playerRef.current?.seekBy(10)}>+10s</button>
    </>
  )
}

MusicPlayerActions: play, pause, togglePlay, playIndex, playId, next, prev, reload, seek, seekBy, setVolume, volumeBy, toggleMute, setPlayMode, cyclePlayMode, setTheme, toggleTheme, setMode, toggleMode, removeTrack, clearTracks, reorderTracks, download, getAudioInfo.

Headless: useMusicPlayer

Build your own UI on the same engine. Render an <audio> element with player.audioProps and wire player.state/player.actions to your controls.

import { useMusicPlayer } from 'tailwind-react-music-player'

function MyPlayer() {
  const { state, actions, audioProps } = useMusicPlayer({
    audioLists,
    onTrackChange: (info) => console.log('now playing', info.track?.name),
  })

  return (
    <div>
      <audio {...audioProps} />
      <p>
        {state.track?.name} — {state.status}
      </p>
      <button onClick={actions.prev}>prev</button>
      <button onClick={actions.togglePlay}>
        {state.status === 'playing' ? 'pause' : 'play'}
      </button>
      <button onClick={actions.next}>next</button>
      <input
        type="range"
        min={0}
        max={state.duration || 0}
        value={state.currentTime}
        onChange={(e) => actions.seek(e.target.valueAsNumber)}
      />
    </div>
  )
}

useMusicPlayer(options) returns { state, actions, audioRef, audioProps } — see MusicPlayerState, MusicPlayerActions and MusicPlayerAudioProps in src/types.ts.

Theming

The player renders under a .trmp-root element carrying data-theme (dark | light), data-mode, data-layout (desktop | mobile) and data-status attributes.

<MusicPlayer theme="auto" /> // follows prefers-color-scheme

CSS custom properties

Every design token is a --trmp-* variable you can override at runtime — globally, or scoped to a subtree:

:root {
  --trmp-color-primary: #ff5500;
}
.my-player-scope {
  --trmp-color-panel: #1e1e2e;
}
VariableDefaultDescription
--trmp-color-primary#31c27cAccent colour
--trmp-color-primary-contrast#05301cText/icons on the accent
--trmp-color-primary-hoverprimary + 15% whiteAccent on hover
--trmp-color-panelrgb(0 0 0 / 75%)Panel background
--trmp-color-panel-mobilergb(0 0 0 / 92%)Mobile panel background
--trmp-color-panel-text#fffPanel text
--trmp-color-panel-mutedrgb(255 255 255 / 60%)Muted text / slider rail
--trmp-color-controller#e6e6e6Mini controller background
--trmp-color-surface#f7f8faScrollbar/button surface
--trmp-color-list-itemrgb(0 0 0 / 10%)Playlist row background
--trmp-color-list-item-activergb(0 0 0 / 35%)Row hover
--trmp-color-list-item-playingrgb(0 0 0 / 35%)Playing row
--trmp-color-track#fffProgress rail
--trmp-color-track-bufferedrgb(0 0 0 / 11%)Buffered progress
--trmp-color-lyricvar(--trmp-color-primary)Floating lyric colour
--trmp-spacing-panel80pxPanel height
--trmp-spacing-panel-header50pxPlaylist header height
--trmp-spacing-controller80pxMini controller size
--trmp-spacing-controller-mobile60pxMini controller size (mobile)
--trmp-spacing-playlist-w / --trmp-spacing-playlist-h480px / 410pxPlaylist drawer size
--trmp-spacing-track-h5pxProgress bar height
--trmp-radius-panel4pxPlaylist corner radius
--trmp-text-lyric / --trmp-text-lyric-mobile36px / 16pxFloating lyric font size
--trmp-ease-playercubic-bezier(0.43, -0.1, 0.16, 1.1)Default easing
--trmp-duration-player350msDefault transition duration
--trmp-shadow-panel0 0 3px #403f3fPanel shadow
--trmp-range-fillper-sliderFilled portion of each range input (set inline by the player)

The light theme is a token remap on .trmp-root[data-theme='light']; the mobile layout remaps on .trmp-root[data-layout='mobile']. To customise a single theme, override tokens inside the corresponding selector:

.trmp-root[data-theme='light'] {
  --trmp-color-primary: #7c3aed;
}

Semantic class hooks

Stable trmp-* classes are part of the public styling API and always present alongside utility classes: .trmp-root, .trmp-panel, .trmp-mobile, .trmp-mini, .trmp-playlist, .trmp-lyric, .trmp-toast, .trmp-cover, .trmp-icon-button, .trmp-theme-switch, .trmp-time, .trmp-track-title, .trmp-range-wrap, .trmp-range, .trmp-sortable-highlight.

Icons

icons accepts per-slot ReactNode overrides. Slots: play, pause, prev, next, loading, volume, mute, download, reload, lyric, playlist, close, delete, minimize, destroy, order, orderLoop, singleLoop, shuffle, collapse, empty, controller.

<MusicPlayer icons={{ play: <MyPlayIcon />, playlist: <MyListIcon /> }} />

defaultIcons is exported if you want to wrap or inspect the defaults.

Locales

Built-in locales: en_US, en_GB, zh_CN (all exported via locales). locale accepts an id or a partial override merged over en_US:

<MusicPlayer locale="zh_CN" />
<MusicPlayer locale={{ play: 'Play now', emptyPlaylist: 'Nothing here yet' }} />

createLocale(partial, base?) and resolveLocale(input) are exported for reuse; the full string list is the Locale type in src/i18n/types.ts.

Accessibility

  • All controls are labelled (aria-label/title) and keyboard-reachable
  • The theme switch is role="switch"; the playlist is a dialog with focus management; lyrics announce via an aria-live overlay
  • prefers-reduced-motion collapses decorative animation

Enable keyboard shortcuts with keyboardShortcuts. They fire on keydown within the player (ignored from text fields and handled events):

KeyAction
SpacePlay / pause
← / →Seek −5s / +5s
↑ / ↓Volume +0.1 / −0.1
mMute / unmute
n / pNext / previous track

Playlist rows additionally support Alt+arrow keyboard reordering. useKeyboardShortcuts is exported if you want the same bindings in a headless UI.

SSR

The module is SSR-importable — it ships a "use client" banner and touches document only inside effects. By default the component renders nothing until the portal target resolves after mount; pass container={null} to render inline without a portal.

Migrating from v4

v5 is a full rewrite — React 19, hooks, ESM-only, new prop names and a new token system. See MIGRATION.md.

Development

See CONTRIBUTING.md.

License

MIT

media-player
music
music-player
react
react-components
reactjs
tailwind
tailwind-css
tailwindcss
typescript
typescript-library
typescript-react

alifeinbinary/tailwind-react-music-player

A beautiful HTML5 responsive music player component for react and styled with Tailwind

TypeScript

0

658 commits

updated Oct 13, 2026

See the code

See what people are saying

SourceMessageScoreDate

I created a modern Music Player, styled with TailwindCSS (r/reactjs)

I have been using the [react-music-player from lijinke666](https://github.com/lijinke666/react-music-player) on my portfolio website since React 16 but it has become abandonware that uses antiquated web technologies, so I took it upon myself to create a modernised version of it, complete with…

0

Oct 13, 2026

README

tailwind-react-music-player

An accessible, themeable HTML5 music player component for React, styled with Tailwind CSS — with a headless useMusicPlayer hook if you want to bring your own UI.

npm version CI bundle size license TypeScript

demo

Live demo — try the player in your browser before installing.

Features

  • Full player UI: mini controller, fixed bottom panel, mobile layout, playlist drawer, floating lyric overlay
  • Headless useMusicPlayer hook — same engine, your own markup
  • Accessible: labelled controls, keyboard shortcuts, focus management, reduced-motion support
  • Themeable: dark / light / auto themes, runtime --trmp-* CSS custom properties, per-slot icon overrides
  • Playlist management: drag-to-sort (SortableJS), remove, reorder, list updates that preserve the current track
  • Lyrics: LRC parsing and sync (parseLrc is exported if you roll your own)
  • Volume fade in/out, perceptual volume mapping, persistence to localStorage
  • Media Session API integration (lock-screen / hardware-key controls)
  • Typed end-to-end; the public API surface is src/types.ts

Requirements

  • React >= 19 (MusicPlayer takes ref as a plain prop — a React 19 feature)
  • ESM-only — no CJS or UMD build; CJS consumers must import() or go through a bundler
  • Node >= 22.19 for the build tooling (the published bundle has no runtime Node dependency)

Install

yarn add tailwind-react-music-player
# or
npm install tailwind-react-music-player
import MusicPlayer from 'tailwind-react-music-player'
import 'tailwind-react-music-player/styles.css' // required — styles are a separate entry point

Quick start

import MusicPlayer from 'tailwind-react-music-player'
import 'tailwind-react-music-player/styles.css'

const audioLists = [
  {
    name: 'Despacito',
    singer: 'Luis Fonsi',
    cover: 'https://example.com/cover.jpg',
    musicSrc: 'https://example.com/despacito.mp3',
    // musicSrc can also resolve lazily:
    // musicSrc: () => fetch('/api/sign').then((r) => r.text()),
    lyric: '[00:00.00]Line one of the lyric',
  },
]

export default function App() {
  return <MusicPlayer audioLists={audioLists} />
}

Props

MusicPlayerProps = UseMusicPlayerOptions & MusicPlayerUIProps — the options below are also accepted directly by useMusicPlayer. src/types.ts is the source of truth.

Playlist, playback and state

Most state can be controlled (value + onChange) or uncontrolled (default*).

PropTypeDefaultDescription
audioListsreadonly AudioTrack[]—Required. The playlist.
playIndexnumber—Controlled index of the playing track
defaultPlayIndexnumber0Initial index (uncontrolled)
onPlayIndexChange(index: number) => void—Fires when the index changes
playModePlayMode—Controlled play mode
defaultPlayModePlayMode'order''order' | 'orderLoop' | 'singleLoop' | 'shufflePlay'
onPlayModeChange(mode: PlayMode) => void—Fires when the play mode changes
themeTheme—Controlled theme
defaultThemeTheme'dark''dark' | 'light' | 'auto' (auto follows prefers-color-scheme)
onThemeChange(theme: Theme) => void—Fires when the theme changes
modePlayerMode—Controlled layout mode
defaultModePlayerMode'mini'¹'mini' | 'full'
onModeChange(mode: PlayerMode) => void—Fires when the mode changes
volumenumber—Controlled volume, slider scale 0..1 (mapped to gain internally)
defaultVolumenumber1Initial volume (uncontrolled)
onVolumeChange(volume: number) => void—Fires when the volume changes
autoPlaybooleantruePlay the first track on load (subject to browser gesture policy)
preload'auto' | 'metadata' | 'none''none'preload attribute on the <audio> element
crossOrigin'anonymous' | 'use-credentials'—crossOrigin attribute on the <audio> element
skipOnErrorbooleantrueSkip to the next track on load/playback errors
restartCurrentOnPrevbooleanfalsePrev restarts the track when past the restart threshold
preserveCurrentOnUpdatebooleanfalseKeep the playing track across audioLists updates
replaceListOnUpdatebooleanfalseReplace the list on audioLists update instead of merging
autoPlayOnListUpdatebooleanfalseAuto-play when audioLists changes
persistboolean | stringfalsePersist state to localStorage; true uses the default key, a string sets a custom key
volumeFadeVolumeFadeOptions—{ fadeIn, fadeOut } durations in ms (skipped on iOS)
mobileMediaQuerystring | null(max-width: 768px) and (orientation: portrait)Media query that selects the mobile UI; null disables
mediaSessionbooleanfalsePublish Media Session metadata and action handlers
random() => numberMath.randomInjectable random source for shuffle (tests)

¹ <MusicPlayer> defaults defaultMode to 'mini'; bare useMusicPlayer defaults to 'full'.

AudioTrack:

interface AudioTrack {
  name?: React.ReactNode
  singer?: React.ReactNode
  cover?: string
  musicSrc: string | (() => Promise<string>)
  lyric?: string
  duration?: number
}

Callbacks

Every callback receives a unified AudioInfo payload (track, index, tracks, currentTime, duration, volume, muted, paused, ended, readyState, networkState, playMode, currentLyric).

PropSignatureDescription
onPlay(info: AudioInfo) => voidPlayback started
onPause(info: AudioInfo) => voidPlayback paused
onEnded(info: AudioInfo) => voidTrack ended
onError(error: MediaError | Error | null, info: AudioInfo) => voidLoad/playback error
onProgress(info: AudioInfo) => voidtimeupdate progress
onSeeked(info: AudioInfo) => voidSeek completed
onAbort(info: AudioInfo) => voidLoad aborted
onTrackChange(info: AudioInfo) => voidCurrent track changed
onTracksChange(tracks: readonly PlaylistItem[], info: AudioInfo) => voidPlaylist contents changed
onLyricChange(line: string, index: number) => voidActive lyric line changed
onReload(info: AudioInfo) => voidReload requested
onBeforeDownload(info: AudioInfo) => DownloadInfo | Promise<DownloadInfo> | voidReturn/resolve a DownloadInfo to customise the download; return nothing for the default { src, filename }
onDownload(info: AudioInfo, download: DownloadInfo) => voidFires after the download resolves
customDownloader(download: DownloadInfo) => voidReplaces the built-in download behaviour (DownloadInfo = { src, filename?, mimeType? })

UI props

PropTypeDefaultDescription
classNamestring—Extra class on the .trmp-root element
styleReact.CSSProperties—Inline style on the root element
containerHTMLElement | (() => HTMLElement) | nulldocument.bodyPortal target; null renders inline (no portal)
localeLocaleId | Partial<Locale>'en_US'Locale id or partial string overrides — see Locales
iconsPartial<PlayerIcons>defaultIconsPer-slot icon overrides — see Icons
draggablebooleantrueMini controller is draggable
dragBoundsstring | DraggableBounds—Drag constraint passed to react-draggable
defaultPosition{ top?, left?, right?, bottom? }{ right: 30, bottom: 30 }Mini controller initial position
allowModeTogglebooleantrueAllow switching mini ⇄ full
responsivebooleantrueSwitch to the mobile UI when mobileMediaQuery matches
seekablebooleantrueProgress bar is interactive
glassBackgroundbooleanfalseFrosted-glass panel background
hideEmptyCoverbooleanfalseHide the cover when a track has none
showMiniCoverbooleantrueShow the cover in the mini controller
showMiniProgressbooleanfalseCircular progress ring on the mini controller
showBufferedbooleantrueShow the buffered-progress bar
showPlayButtonbooleantrueShow the play/pause button
showReloadbooleantrueShow the reload button
showDownloadbooleantrueShow the download button
showPlayModebooleantrueShow the play-mode toggle
showThemeSwitchbooleantrueShow the light/dark theme switch
showLyricbooleanfalseShow the lyric toggle (and enable the lyric overlay)
showDestroybooleanfalseShow the destroy button
removablebooleantrueTracks can be removed from the playlist
sortableboolean | SortableOptionsfalseDrag-to-sort the playlist; an object is forwarded to SortableJS
keyboardShortcutsbooleanfalseEnable keyboard shortcuts — see Accessibility
playModeTipDurationnumber600How long the play-mode toast stays visible (ms)
extraControlsReact.ReactNode—Custom content rendered into the controls row
lyricClassNamestring—Extra class on the lyric overlay
renderTrackTitle(info: AudioInfo, isMobile: boolean) => ReactNode—Custom title renderer (also used for the <audio> title)
onCoverClick(info: AudioInfo) => void—Cover image clicked
onPlaylistToggle(open: boolean) => void—Playlist drawer opened/closed
onBeforeDestroy(info: AudioInfo) => boolean | void | Promise<boolean | void>—Return/resolve false to cancel destruction
onDestroyed(info: AudioInfo) => void—Player destroyed

Ref handle

MusicPlayer takes a React 19 ref prop exposing MusicPlayerHandle: every MusicPlayerActions method, the live audio element, and destroy().

import { useRef } from 'react'
import MusicPlayer, {
  type MusicPlayerHandle,
} from 'tailwind-react-music-player'

function App() {
  const playerRef = useRef<MusicPlayerHandle>(null)

  return (
    <>
      <MusicPlayer ref={playerRef} audioLists={audioLists} />
      <button onClick={() => playerRef.current?.togglePlay()}>toggle</button>
      <button onClick={() => playerRef.current?.seekBy(10)}>+10s</button>
    </>
  )
}

MusicPlayerActions: play, pause, togglePlay, playIndex, playId, next, prev, reload, seek, seekBy, setVolume, volumeBy, toggleMute, setPlayMode, cyclePlayMode, setTheme, toggleTheme, setMode, toggleMode, removeTrack, clearTracks, reorderTracks, download, getAudioInfo.

Headless: useMusicPlayer

Build your own UI on the same engine. Render an <audio> element with player.audioProps and wire player.state/player.actions to your controls.

import { useMusicPlayer } from 'tailwind-react-music-player'

function MyPlayer() {
  const { state, actions, audioProps } = useMusicPlayer({
    audioLists,
    onTrackChange: (info) => console.log('now playing', info.track?.name),
  })

  return (
    <div>
      <audio {...audioProps} />
      <p>
        {state.track?.name} — {state.status}
      </p>
      <button onClick={actions.prev}>prev</button>
      <button onClick={actions.togglePlay}>
        {state.status === 'playing' ? 'pause' : 'play'}
      </button>
      <button onClick={actions.next}>next</button>
      <input
        type="range"
        min={0}
        max={state.duration || 0}
        value={state.currentTime}
        onChange={(e) => actions.seek(e.target.valueAsNumber)}
      />
    </div>
  )
}

useMusicPlayer(options) returns { state, actions, audioRef, audioProps } — see MusicPlayerState, MusicPlayerActions and MusicPlayerAudioProps in src/types.ts.

Theming

The player renders under a .trmp-root element carrying data-theme (dark | light), data-mode, data-layout (desktop | mobile) and data-status attributes.

<MusicPlayer theme="auto" /> // follows prefers-color-scheme

CSS custom properties

Every design token is a --trmp-* variable you can override at runtime — globally, or scoped to a subtree:

:root {
  --trmp-color-primary: #ff5500;
}
.my-player-scope {
  --trmp-color-panel: #1e1e2e;
}
VariableDefaultDescription
--trmp-color-primary#31c27cAccent colour
--trmp-color-primary-contrast#05301cText/icons on the accent
--trmp-color-primary-hoverprimary + 15% whiteAccent on hover
--trmp-color-panelrgb(0 0 0 / 75%)Panel background
--trmp-color-panel-mobilergb(0 0 0 / 92%)Mobile panel background
--trmp-color-panel-text#fffPanel text
--trmp-color-panel-mutedrgb(255 255 255 / 60%)Muted text / slider rail
--trmp-color-controller#e6e6e6Mini controller background
--trmp-color-surface#f7f8faScrollbar/button surface
--trmp-color-list-itemrgb(0 0 0 / 10%)Playlist row background
--trmp-color-list-item-activergb(0 0 0 / 35%)Row hover
--trmp-color-list-item-playingrgb(0 0 0 / 35%)Playing row
--trmp-color-track#fffProgress rail
--trmp-color-track-bufferedrgb(0 0 0 / 11%)Buffered progress
--trmp-color-lyricvar(--trmp-color-primary)Floating lyric colour
--trmp-spacing-panel80pxPanel height
--trmp-spacing-panel-header50pxPlaylist header height
--trmp-spacing-controller80pxMini controller size
--trmp-spacing-controller-mobile60pxMini controller size (mobile)
--trmp-spacing-playlist-w / --trmp-spacing-playlist-h480px / 410pxPlaylist drawer size
--trmp-spacing-track-h5pxProgress bar height
--trmp-radius-panel4pxPlaylist corner radius
--trmp-text-lyric / --trmp-text-lyric-mobile36px / 16pxFloating lyric font size
--trmp-ease-playercubic-bezier(0.43, -0.1, 0.16, 1.1)Default easing
--trmp-duration-player350msDefault transition duration
--trmp-shadow-panel0 0 3px #403f3fPanel shadow
--trmp-range-fillper-sliderFilled portion of each range input (set inline by the player)

The light theme is a token remap on .trmp-root[data-theme='light']; the mobile layout remaps on .trmp-root[data-layout='mobile']. To customise a single theme, override tokens inside the corresponding selector:

.trmp-root[data-theme='light'] {
  --trmp-color-primary: #7c3aed;
}

Semantic class hooks

Stable trmp-* classes are part of the public styling API and always present alongside utility classes: .trmp-root, .trmp-panel, .trmp-mobile, .trmp-mini, .trmp-playlist, .trmp-lyric, .trmp-toast, .trmp-cover, .trmp-icon-button, .trmp-theme-switch, .trmp-time, .trmp-track-title, .trmp-range-wrap, .trmp-range, .trmp-sortable-highlight.

Icons

icons accepts per-slot ReactNode overrides. Slots: play, pause, prev, next, loading, volume, mute, download, reload, lyric, playlist, close, delete, minimize, destroy, order, orderLoop, singleLoop, shuffle, collapse, empty, controller.

<MusicPlayer icons={{ play: <MyPlayIcon />, playlist: <MyListIcon /> }} />

defaultIcons is exported if you want to wrap or inspect the defaults.

Locales

Built-in locales: en_US, en_GB, zh_CN (all exported via locales). locale accepts an id or a partial override merged over en_US:

<MusicPlayer locale="zh_CN" />
<MusicPlayer locale={{ play: 'Play now', emptyPlaylist: 'Nothing here yet' }} />

createLocale(partial, base?) and resolveLocale(input) are exported for reuse; the full string list is the Locale type in src/i18n/types.ts.

Accessibility

  • All controls are labelled (aria-label/title) and keyboard-reachable
  • The theme switch is role="switch"; the playlist is a dialog with focus management; lyrics announce via an aria-live overlay
  • prefers-reduced-motion collapses decorative animation

Enable keyboard shortcuts with keyboardShortcuts. They fire on keydown within the player (ignored from text fields and handled events):

KeyAction
SpacePlay / pause
← / →Seek −5s / +5s
↑ / ↓Volume +0.1 / −0.1
mMute / unmute
n / pNext / previous track

Playlist rows additionally support Alt+arrow keyboard reordering. useKeyboardShortcuts is exported if you want the same bindings in a headless UI.

SSR

The module is SSR-importable — it ships a "use client" banner and touches document only inside effects. By default the component renders nothing until the portal target resolves after mount; pass container={null} to render inline without a portal.

Migrating from v4

v5 is a full rewrite — React 19, hooks, ESM-only, new prop names and a new token system. See MIGRATION.md.

Development

See CONTRIBUTING.md.

License

MIT

media-player
music
music-player
react
react-components
reactjs
tailwind
tailwind-css
tailwindcss
typescript
typescript-library
typescript-react

Project health

Latest release

5.0.2 · Oct 13, 2026

Releases this year

3