# Music Toggle Button

> Play/pause toggle with animated sound-wave bars while playing.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/music-toggle-button
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/music-toggle-button.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/music-toggle-button-demo.tsx"
import { MusicToggleButton } from "@/components/music-toggle-button"

export default function MusicToggleButtonDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-4 p-8">
      <MusicToggleButton />
      <MusicToggleButton defaultPlaying />
    </div>
  )
}
```

## Source

```tsx title="components/music-toggle-button.tsx"
"use client"

import * as React from "react"
import { Pause, Play } from "lucide-react"

import { cn } from "@/lib/utils"

const css = `@keyframes music-bar{0%,100%{transform:scaleY(.25)}50%{transform:scaleY(1)}}@media (prefers-reduced-motion:reduce){.music-bar{animation:none!important;transform:scaleY(.6)}}`

const BARS = [
  { delay: "0ms", duration: "900ms" },
  { delay: "-300ms", duration: "700ms" },
  { delay: "-600ms", duration: "1000ms" },
  { delay: "-150ms", duration: "800ms" },
]

export interface MusicToggleButtonProps extends Omit<React.ComponentProps<"button">, "onChange"> {
  defaultPlaying?: boolean
  onPlayingChange?: (playing: boolean) => void
}

export function MusicToggleButton({
  defaultPlaying = false,
  onPlayingChange,
  className,
  onClick,
  ...props
}: MusicToggleButtonProps) {
  const [playing, setPlaying] = React.useState(defaultPlaying)

  return (
    <>
      <style href="music-toggle-button" precedence="default">
        {css}
      </style>
      <button
        type="button"
        aria-pressed={playing}
        aria-label={playing ? "Now playing, pause music" : "Paused, play music"}
        className={cn(
          "inline-flex h-11 items-center gap-3 rounded-full border border-border bg-background pr-5 pl-2 text-sm font-medium text-foreground",
          "transition-colors duration-200 hover:bg-muted",
          "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none",
          "disabled:pointer-events-none disabled:opacity-50",
          className
        )}
        onClick={(event) => {
          const next = !playing
          setPlaying(next)
          onPlayingChange?.(next)
          onClick?.(event)
        }}
        {...props}
      >
        <span className="flex size-8 items-center justify-center rounded-full bg-foreground text-background">
          {playing ? <Pause className="size-3.5 fill-current" /> : <Play className="size-3.5 translate-x-px fill-current" />}
        </span>
        <span aria-hidden className="flex h-5 items-center gap-[3px]">
          {BARS.map((bar, index) => (
            <span
              key={index}
              className="music-bar h-full w-[3px] origin-center rounded-full bg-primary"
              style={{
                transform: "scaleY(.25)",
                animation: playing ? `music-bar ${bar.duration} ease-in-out ${bar.delay} infinite` : undefined,
              }}
            />
          ))}
        </span>
        <span>{playing ? "Now playing" : "Paused"}</span>
      </button>
    </>
  )
}
```
