npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chat-voice-input

v0.2.0

Published

Composable React voice input for live transcription.

Downloads

352

Readme

Chat Voice Input

A composable voice input for chat composers, with a microphone button, live waveform, timer, and built-in loading, recording, and error states.

Use the AI SDK adapter, native browser speech recognition, or connect any service or local model through the Transcriber interface. The component coordinates capture, stopping, empty results, failures, and cleanup. Audio is never stored by the package.

Install

pnpm add chat-voice-input

React 18 or newer is required.

Use the AI SDK transcriber

The optional AI SDK adapter streams 24 kHz PCM audio through Vercel AI Gateway:

pnpm add ai @ai-sdk/gateway
import { useState } from "react";
import ChatVoiceInput from "chat-voice-input";
import { createAiSdkTranscriber } from "chat-voice-input/ai-sdk";
import "chat-voice-input/style.css";

const transcriber = createAiSdkTranscriber();

function Composer() {
  const [value, setValue] = useState("");

  function appendTranscript(delta: string): void {
    setValue((current) => current + delta);
  }

  return (
    <>
      <textarea onChange={(event) => setValue(event.target.value)} value={value} />
      <ChatVoiceInput
        disabled={false}
        onDelta={appendTranscript}
        transcriber={transcriber}
      />
    </>
  );
}

ChatVoiceInput emits transcription deltas without owning or modifying the editor value. This example appends each delta verbatim; spacing and punctuation come from the transcriber. The consumer can instead decide where and how to apply each delta.

The adapter requests a short-lived token from POST /api/transcription. Add that route to your server:

import { createTranscriptionTokenResponse } from "chat-voice-input/server";

export function POST(): Promise<Response> {
  return createTranscriptionTokenResponse({
    apiKey: process.env.AI_GATEWAY_API_KEY,
  });
}

The provider requests the microphone immediately. The adapter starts capturing PCM and requests its token as soon as the stream is available. Audio captured while the token is pending is consumed when transcription connects.

Keep AI_GATEWAY_API_KEY on the server. Protect the token route with authentication and rate limiting because it spends against your Gateway account. Use tokenEndpoint to configure another route.

Use the native browser transcriber

For a setup without a backend, API key, or additional dependency, use the browser's built-in speech recognition:

import ChatVoiceInput, { createNativeTranscriber } from "chat-voice-input";

const transcriber = createNativeTranscriber();

Pass it to ChatVoiceInput exactly like the AI SDK adapter. You can optionally set the recognition language; otherwise it uses navigator.language:

const transcriber = createNativeTranscriber({ language: "es-ES" });

This adapter uses SpeechRecognition or webkitSpeechRecognition, so availability and transcription quality depend on the browser. On WebKit, speech recognition controls its own audio capture because the browser API cannot consume a provided MediaStream.

Use a custom transcriber

Implement the small Transcriber contract and pass the object to the component:

import type { Transcriber } from "chat-voice-input";

const transcriber: Transcriber = {
  async start({ stream, onDelta, signal }) {
    const recording = await startYourTranscription({ stream, onDelta, signal });

    return {
      stop: recording.stop,
      text: recording.text,
    };
  },
};

The provider opens and closes the microphone. The transcriber receives that stream with an abort signal and a callback for text deltas. It returns a stop function and a promise for the final text.

If your service needs PCM, use the same converter as the AI SDK adapter:

import { createPcmStream } from "chat-voice-input/audio";

const pcm = await createPcmStream(stream); // 24 kHz s16le by default

Pass { sampleRate } to select another rate. Closing the PCM stream releases only its audio graph; microphone ownership stays with the provider.

Compose your own layout

import ChatVoiceInput, { useChatVoiceInput } from "chat-voice-input";

<ChatVoiceInput.Provider
  disabled={disabled}
  onDelta={appendTranscript}
  transcriber={transcriber}
>
  <ChatVoiceInput.Error />
  <ChatVoiceInput.Waveform />
  <ChatVoiceInput.Timer />
  <ChatVoiceInput.Button />
</ChatVoiceInput.Provider>;

useChatVoiceInput() exposes status, stream, start, and stop. Every component is also available as a named export.

The optional stylesheet contains only the built-in control styles and exposes --chat-voice-input-button-background, --chat-voice-input-button-background-hover, and --chat-voice-input-muted for theming. The component does not own its surrounding layout.

Covered edge cases

ChatVoiceInputProvider handles every Transcriber through the same lifecycle. Adapters report microphone and transcription failures through that contract. Any failure shows Voice input is unavailable. and changes the button to Retry.

| Scenario | Behavior | | --- | --- | | Voice input is disabled | Disables Start; does not call the transcriber | | Waiting for microphone permission or transcriber start | Shows Loading; disables the button; shows no recording UI | | Capture and transcription are active | Shows Stop, waveform, and timer | | User denies microphone permission | Rejects the start; shows the error and Retry | | Native transcriber is selected but unavailable | Rejects before requesting the microphone; shows the error and Retry | | Microphone is unavailable or busy | Rejects the start; shows the error and Retry | | Transcriber emits text | Calls onDelta immediately | | User presses Stop | Stops capture; shows Loading until final text settles | | Its containing form is submitted | Stops an active or pending session | | Transcriber finishes on its own | Stops capture, emits final text if no deltas arrived, and returns to idle | | Transcriber ends without text | Returns to idle without an error or delta | | Remote transcriber is still connecting | Captures audio; shows recording only after the adapter confirms it started | | Transcriber fails during recording | Aborts capture; does not retract emitted deltas; shows the error and Retry | | Microphone disconnects during recording | Aborts transcription; does not retract emitted deltas; shows the error and Retry | | Voice input is disabled or unmounted while active | Aborts capture and ignores late results |

iOS limitation

In Safari on iOS and iPadOS, native SpeechRecognition can conflict with the component's microphone stream and mute an audio track. After stopping, later recording or recognition attempts may receive no audio. See the WebKit reports for SpeechRecognition muting an existing track and subsequent captures producing no audio. Prefer a stream-based transcriber such as the AI SDK adapter on these platforms.

Development

pnpm install
pnpm check
pnpm test
pnpm build

Run the demo with pnpm demo. Its selector switches between native browser transcription and the streaming AI SDK models available through AI Gateway.

License

MIT