Voice Input Button
Voice Input Button is a React and Tailwind CSS component from Kokonut UI, licensed MIT. Copy it and paste it into your project.
Kokonut UI
MIT
other
What it is
Voice Input Button is a React and Tailwind CSS component from Kokonut UI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i motion lucide-react
Licence
This component comes from Kokonut UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Voice Input Button
"use client";
/**
* @author: @kokonutui
* @description: AI Voice
* @version: 1.0.0
* @date: 2025-06-26
* @license: MIT
* @website: https://kokonutui.com
* @github: https://github.com/kokonut-labs/kokonutui
*/
import { Mic } from "lucide-react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
export default function AI_Voice() {
const [submitted, setSubmitted] = useState(false);
const [time, setTime] = useState(0);
const [isClient, setIsClient] = useState(false);
const [isDemo, setIsDemo] = useState(true);
useEffect(() => {
setIsClient(true);
}, []);
useEffect(() => {
let intervalId: NodeJS.Timeout;
if (submitted) {
intervalId = setInterval(() => {
setTime((t) => t + 1);
}, 1000);
} else {
setTime(0);
}
return () => clearInterval(intervalId);
}, [submitted]);
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, "0")}:${secs
.toString()
.padStart(2, "0")}`;
};
/**
* Remove that, only used for demo
*/
useEffect(() => {
if (!isDemo) return;
let timeoutId: NodeJS.Timeout;
const runAnimation = () => {
setSubmitted(true);
timeoutId = setTimeout(() => {
setSubmitted(false);
timeoutId = setTimeout(runAnimation, 1000);
}, 3000);
};
const initialTimeout = setTimeout(runAnimation, 100);
return () => {
clearTimeout(timeoutId);
clearTimeout(initialTimeout);
};
}, [isDemo]);
const handleClick = () => {
if (isDemo) {
setIsDemo(false);
setSubmitted(false);
} else {
setSubmitted((prev) => !prev);
}
};
return (
<div className="w-full py-4">
<div className="relative mx-auto flex w-full max-w-xl flex-col items-center gap-2">
<button
className={cn(
"group flex h-16 w-16 items-center justify-center rounded-xl transition-colors",
submitted
? "bg-none"
: "bg-none hover:bg-black/5 dark:hover:bg-white/5"
)}
onClick={handleClick}
type="button"
>
{submitted ? (
<div
className="pointer-events-auto h-6 w-6 animate-spin cursor-pointer rounded-sm bg-black dark:bg-white"
style={{ animationDuration: "3s" }}
/>
) : (
<Mic className="h-6 w-6 text-black/90 dark:text-white/90" />
)}
</button>
<span
className={cn(
"font-mono text-sm transition-opacity duration-300",
submitted
? "text-black/70 dark:text-white/70"
: "text-black/30 dark:text-white/30"
)}
>
{formatTime(time)}
</span>
<div className="flex h-4 w-64 items-center justify-center gap-0.5">
{[...Array(48)].map((_, i) => (
<div
className={cn(
"w-0.5 rounded-full transition-all duration-300",
submitted
? "animate-pulse bg-black/50 dark:bg-white/50"
: "h-1 bg-black/10 dark:bg-white/10"
)}
key={i}
style={
submitted && isClient
? {
height: `${20 + Math.random() * 80}%`,
animationDelay: `${i * 0.05}s`,
}
: undefined
}
/>
))}
</div>
<p className="h-4 text-black/70 text-xs dark:text-white/70">
{submitted ? "Listening..." : "Click to speak"}
</p>
</div>
</div>
);
}