Speech input

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

Speech capture requires application code.

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