Speech input

Starts and stops native speech capture, with final text, optional interim results, and application transcription of recorded audio.

Final speech appears here. Nothing is sent to a conversation.

Add text by speaking

Final speech appears here. Nothing is sent to a conversation.

Activate speech input to use this browser’s recognition when available. Final text stays in this example. Applications can provide onAudioRecorded to transcribe a recording in browsers that need a fallback.

Use it for

  • Capture speech only after the user activates the button. Stop processes the recording; Cancel discards pending capture or transcription.
  • Native speech recognition is feature-detected. Supply onAudioRecorded for browsers that need a MediaRecorder transcription fallback.
  • The fallback negotiates an available media type and passes actual audio bytes plus an AbortSignal to the application.
  • Use mode=recording and deviceId for a specific input. Recognition mode follows the browser's input choice.
  • maxDuration defaults to 120 seconds; configuration changes and unmount cancel owned capture resources.
  • Provider credentials and transcription requests belong to the application. CSS-only markup does not record audio.

Avoid

  • Claiming browser speech recognition always works offline.
  • Automatically sending transcribed text to a conversation without the application's send action.

React package

npm install @noorddev/vlak-react

Load the stylesheet once at your app root, then import the components you use.

import "@noorddev/vlak-react/css";
import { SpeechInput } from "@noorddev/vlak-react";

Per-component imports are available at @noorddev/vlak-react/components/speech-input.

Copy the source

npx @noorddev/vlak-cli add speech-input

Adds the component and its dependencies to components/vlak/. Follow the StyleX setup to compile the source in your application.

shadcn registry

npx shadcn add https://vlak.dev/r/speech-input.json

Installs the same source through the shadcn CLI.

import { SpeechInput } from "@noorddev/vlak-react";

export function Dictation({ onTranscript, endpoint }: {
  onTranscript: (text: string) => void;
  endpoint: string; // The application's audio transcription endpoint.
}) {
  return <SpeechInput lang="en-US" onTranscript={onTranscript}
    onAudioRecorded={async (audio, { signal }) => {
      const body = new FormData();
      body.append("audio", audio);
      const response = await fetch(endpoint, { method: "POST", body, signal });
      if (!response.ok) throw new Error("Transcription failed. Try again.");
      const result: unknown = await response.json();
      if (!result || typeof result !== "object" || !("text" in result) || typeof result.text !== "string") {
        throw new Error("The transcription response must contain text.");
      }
      return result.text;
    }}
  />;
}

See the integration example to compose these components with your application's model and data.

SpeechInput

User-activated speech capture with an application-owned transcription fallback.

PropTypeDefaultDescription
variant"primary" | "ghost" | "subtle"Solid ink primary, hairline ghost, or borderless subtle. One primary per view.
size"default" | "sm" | "icon"Icon buttons stay square at every breakpoint. Supply an accessible name.
groupedbooleanFlush into a ButtonGroup: no own stroke, one ink seam.
onTranscriptrequired(text: string) => void
onInterimTranscript(text: string) => void
onAudioRecorded(audio: Blob, context: { signal: AbortSignal; }) => Promise<string>
onStatusChange(status: SpeechInputStatus) => void
onError(error: Error) => void
mode"auto" | "recognition" | "recording""auto"
deviceIdstring
langstring"en-US"
maxDurationnumber120Stops and processes a recording after this many seconds. Defaults to 120.

Also accepts ButtonHTMLAttributes<HTMLButtonElement>.

The ref reaches HTMLButtonElement.

KeysDoes
TabFocuses the speech control when available
Enter, SpaceStarts, stops, or cancels speech input according to its current state
  • The native button has a state-specific label, aria-pressed, and a 44px target.
  • A described status region explains listening, processing, unavailable support and recoverable errors.
  • The button ref and native button attributes pass through. Unsupported capture is disabled with an explanation.
  • Owned streams, listeners, recognition and processing are canceled on cleanup; late results are ignored.

The same styles are available as CSS. Native HTML provides the static presentation; React or application code supplies state updates and actions.

npm install @noorddev/vlak
import "@noorddev/vlak/css";
<span class="rs-speech-input"><button class="rs-btn-subtle" type="button" aria-pressed="false">Start speech input</button><span class="rs-speech-input-message" role="status">Speech capture requires application code.</span></span>

Classes

.rs-speech-input.rs-speech-input-message.rs-speech-input-icon