Keep the flow of writing.
Speak where the cursor is. Select a phrase to replace it. Undo as usual.
Explore editing behaviorDictation for your inputs and textareas.
Keep your components, styling, and editing behavior.
The demo relays audio through voiceinput.dev to enforce its limits, then sends it to OpenAI. It stores neither audio nor transcripts; SDK integrations connect directly to providers.
Install two packages, then add a hook to your textarea or use VoiceTextarea. Your styling and form logic stay yours.
"use client";
import { useState } from "react";
import { VoiceTextarea } from "@voiceinput/react";
import { openai } from "@voiceinput/openai";
import "@voiceinput/react/styles.css";
const provider = openai({ tokenEndpoint: "/api/voice-token" });
export function Composer() {
const [text, setText] = useState("");
return (
<VoiceTextarea
aria-label="Message"
placeholder="Write a message…"
value={text}
onValueChange={setText}
voice={{ provider }}
/>
);
}"use client";
import { getVoiceInputErrorMessage, useVoiceInput } from "@voiceinput/react";
import { openai } from "@voiceinput/openai";
const provider = openai({ tokenEndpoint: "/api/voice-token" });
export function Composer() {
const { targetRef, getTriggerProps, status, error } = useVoiceInput({
provider,
});
const active = status !== "idle" && status !== "error";
return (
<>
<textarea aria-label="Message" name="message" ref={targetRef} />
<button {...getTriggerProps()}>{active ? "Stop" : "Speak"}</button>
{error && <p role="alert">{getVoiceInputErrorMessage(error)}</p>}
</>
);
}import { createOpenAITokenHandler } from "@voiceinput/openai/server";
import { getCurrentUser } from "./app-auth"; // your existing session check
export const runtime = "nodejs";
const appOrigin = new URL(process.env.APP_ORIGIN!).origin;
export const POST = createOpenAITokenHandler({
apiKey: process.env.OPENAI_API_KEY!,
authorize: async (request) => {
if (request.headers.get("origin") !== appOrigin) return null;
const user = await getCurrentUser(request);
return user ? { subject: user.id } : null;
},
});app-auth contains your authentication and quota helpers. Complete server setup
import { Hono } from "hono";
import { createOpenAITokenHandler } from "@voiceinput/openai/server";
import { getCurrentUser } from "./app-auth";
const app = new Hono();
const appOrigin = new URL(process.env.APP_ORIGIN!).origin;
const issueToken = createOpenAITokenHandler({
apiKey: process.env.OPENAI_API_KEY!,
authorize: async (request) => {
if (request.headers.get("origin") !== appOrigin) return null;
const user = await getCurrentUser(request);
return user ? { subject: user.id } : null;
},
});
app.post("/api/voice-token", (c) => issueToken(c.req.raw));
export default app;app-auth contains your authentication and quota helpers. Complete server setup
Speak where the cursor is. Select a phrase to replace it. Undo as usual.
Explore editing behaviorYour server issues a temporary credential. Audio streams from your app straight to OpenAI, ElevenLabs, or Deepgram. Switch providers without touching your field.
See how it works