Components / Audio
Audio Recorder
Free Audio React component — AudioRecorderWidget. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
Dark recording card with title, date, animated red waveform, and elapsed time. Play and pause feel like a native voice memo app.
Preview
New Audio
12.8.24
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/audio/audio-recorder-widget.tsxin your project. - 4
Import and render:
Exampleimport { AudioRecorderWidget } from "@/components/audio/audio-recorder-widget";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/audio/audio-recorder-widget.tsx
"use client";import {forwardRef,useEffect,useState,type ComponentPropsWithoutRef,} from "react";import { cn } from "@/lib/cn";import { Pause, Play } from "lucide-react";const BAR_COUNT = 12;const BAR_IDS = Array.from({ length: BAR_COUNT }, (_, bar) => bar);export type AudioRecorderWidgetProps = Readonly<{title?: string;date?: string;} & ComponentPropsWithoutRef<"div">>;// Production-ready Audio Recorder component — styled with Tailwind CSS.export const AudioRecorderWidget = forwardRef<HTMLDivElement,AudioRecorderWidgetProps>(({ className, title = "New Audio", date = "12.8.24", ...props }, ref) => {const [recording, setRecording] = useState(true);const [bars, setBars] = useState<number[]>(() => BAR_IDS.map(() => 30));useEffect(() => {if (!recording) return;const timer = globalThis.setInterval(() => {setBars(BAR_IDS.map(() => 20 + Math.random() * 60));}, 120);return () => globalThis.clearInterval(timer);}, [recording]);return (<divref={ref}data-slot="audio-recorder-widget"className={cn("flex h-44 w-44 flex-col justify-between rounded-3xl bg-neutral-900 p-4 font-sans text-white shadow-lg",className,)}{...props}><div><p className="text-sm font-semibold">{title}</p><p className="text-[11px] text-neutral-400">{date}</p></div><div className="flex h-10 items-end justify-center gap-0.5">{BAR_IDS.map((bar) => (<spankey={bar}className="w-1.5 rounded-full bg-red-500 transition-all duration-100"style={{ height: `${bars[bar]}%` }}/>))}</div><div className="flex items-center justify-between"><span className="font-mono text-xs text-neutral-300">01:12:25</span><buttontype="button"onClick={() => setRecording(!recording)}aria-label={recording ? "Pause recording" : "Resume recording"}aria-pressed={recording}className={cn("flex h-9 w-9 cursor-pointer items-center justify-center rounded-full text-white transition-transform hover:scale-105 active:scale-95",recording ? "bg-red-500" : "bg-neutral-700",)}>{recording ? (<Pause size={14} fill="white" />) : (<Play size={14} fill="white" />)}</button></div></div>);});AudioRecorderWidget.displayName = "AudioRecorderWidget";