Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

AI Kit

Voice Input

AI Kit

Voice Input React component for Tailwind CSS v4

Usage MeterWorkflow Canvas
Live props
Appearance
variant
size
tone
color
radius
barsAuto
Content
transcript
showTranscript
showWaveform
showTimer
State
defaultStatus
error
speaking
disabled
Behavior
mode
audio
transcribe
lang
maxDuration120s
minDuration500ms
haptics
locale
Code
import { VoiceInput } from "@/components/ui/voice-input"

<VoiceInput />
  1. Home
  2. Components
  3. AI Kit
  4. Voice Input
Pro

Voice Input React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React voice input: a mic that grows into a recording pill with a live waveform and transcript, a full-width bar, or a voice orb.

Tap the mic and, once the browser has your permission (asked only after that first tap; the mic spins meanwhile and a desktop tooltip says to allow it), it grows on a smooth spring into a pill: Cancel, a pulsing red dot, a waveform that ripples with your voice, a timer and Send, and it shrinks back into the mic when you are done. The words you say appear above it as the browser hears them, the unsure ones muted; Send hands the app the audio and the transcript, Cancel or Escape throws it away. Ten seconds before maxDuration the timer turns amber and pulses, and at the limit the recording sends itself. In hold mode you press and hold to talk and let go to send; slide more than 80px toward the start, or off the pill, and it tints red with a tick of haptics, so letting go cancels instead. A tap that is too short says so and fades, with the mic still there for the next try. A blocked or missing microphone explains itself in place with Try again, and a send your promise rejects can be sent again. The bar variant is always there at full width, and a tap anywhere on it starts; the orb variant is a voice mode, listening, thinking while your promise runs and blooming with getLevel while the app talks back. With a keyboard, Tab to the mic and press Enter or Space; focus moves to Send while recording, Escape cancels (or closes an error), and in hold mode holding Space records and releasing it sends while Enter works like a tap. Screen readers hear Listening, Transcribing and the recorded length once, never the ticking timer. On phones every target is 44px, the held mic stops the page from scrolling, and a halo under your finger breathes with your voice. Reduced motion swaps the morph for a cross-fade and the waveform for a still level meter.

Voice Input is a Pro React component in the React AI components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add voice-input, together with Button, Matrix Orb, Spinner, Tooltip, useControllableState and 7 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add voice-input

Voice Input is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import VoiceInput from @/components/ui/voice-input.

"use client"

import { useState } from "react"
import { VoiceInput, type VoiceInputResult } from "@/components/ui/voice-input"

export function VoiceNote({ clientId }: { clientId: string }) {
  const [note, setNote] = useState("")

  async function save(result: VoiceInputResult) {
    if (result.transcript) setNote((text) => [text, result.transcript].filter(Boolean).join(" "))
    if (!result.blob) return
    const body = new FormData()
    body.append("audio", result.blob, "voice-note.webm")
    const response = await fetch(`/api/clients/${clientId}/voice-notes`, { method: "POST", body })
    if (!response.ok) throw new Error("Upload failed")
  }

  return (
    <div className="flex flex-col gap-3">
      <p className="text-sm">{note || "No note yet"}</p>
      <VoiceInput
        variant="bar"
        lang="en-US"
        maxDuration={180}
        onRecordingComplete={save}
      />
    </div>
  )
}

Props

  • variantdefault "button"
    "button" | "bar" | "orb"

    button: a mic icon button that expands in place into the recording pill, for composers and toolbars. bar: the full-width bar, always visible like WhatsApp's, with the transcript above it. orb: a MatrixOrb voice mode that listens, thinks and blooms while the app talks back.

  • sizedefault "md"
    "sm" | "md" | "lg"

    The mic is the button's icon-sm / icon / icon-lg (32 / 40 / 48px, 44px on touch for md) and the pill keeps its height, the bar is 40 / 48 / 56px tall, the orb 160 / 220 / 280px wide.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Colors the waveform, the Send button, the bar's mic and the orb. The recording dot stays red whatever the tone, because red means recording.

  • colordefault ""
    string

    Any CSS color. Replaces the tone for the waveform, the buttons and the orb (the orb's canvas gets the resolved color, so oklch and CSS variables work too). Leave empty to use the tone.

  • radiusdefault "full"
    "none" | "sm" | "md" | "lg" | "full"

    Corners of the mic, the pill and the bar. The buttons inside the pill take its corner minus their 4px inset, so every size reads as one shape.

  • barsdefault 0
    number

    How many waveform bars. 0 fits the width with 3px bars and 2px gaps; a fixed count spreads the bars across the pill.

  • transcriptdefault ""
    string

    Words the app streams in while recording (a server transcriber) or while speaking (the answer in voice mode). Wins over the browser's recognizer and becomes the result's transcript.

  • showTranscriptdefault true
    boolean

    Shows the live transcript: above the pill and the bar (four lines, scrolled to the newest words), under the orb in large centered type. Final words in the text color, interim ones muted.

  • showWaveformdefault true
    boolean

    Shows the live waveform in the pill. Hidden, the pill says Recording in its place. In hold mode the slide hint takes this spot and the level shows as a halo around the held mic.

  • showTimerdefault true
    boolean

    Shows the elapsed time (0:07). It turns amber and pulses in the last 10 seconds before maxDuration.

  • defaultStatusdefault "idle"
    "idle" | "requesting" | "recording" | "processing" | "error"

    The starting status. recording without a tap never opens the microphone: the timer runs, getLevel drives the visuals and Send hands over the length and the transcript prop, which is how demos and screenshots show the pill.

  • status
    "idle" | "requesting" | "recording" | "processing" | "error"

    Controlled status, with onStatusChange. Drive processing yourself while a server transcribes, or set idle to stop listening from outside (the recording is discarded).

  • onStatusChange
    (status: VoiceInputStatus) => void

    Fires on every change: requesting after the tap, recording, processing after Send, idle or error.

  • errordefault ""
    "denied" | "noMic" | "unsupported" | "tooShort" | "interrupted" | "failed" | string

    What status error shows. A kind picks its sentence from labels (denied, noMic, unsupported, tooShort, interrupted, failed); any other text shows as it is, like a message from your server. Empty shows the component's own error.

  • speakingdefault false
    boolean

    Orb variant: the app is talking back. The orb blooms with getLevel (your text to speech output), the status reads Speaking and the transcript prop shows the answer. A tap on the mic interrupts it.

  • disableddefault false
    boolean

    Dims everything and blocks starting; a recording in progress is canceled.

  • modedefault "toggle"
    "toggle" | "hold"

    toggle: tap to start, then Send or Cancel. hold: push to talk, release to send, slide away (or Escape) to cancel; the held element sets touch-action none and keeps the pointer while it turns into the pill, and Space works the same way from the keyboard.

  • audiodefault true
    boolean

    Records audio with MediaRecorder (audio/webm with Opus, audio/mp4 on Safari) and hands the Blob to onRecordingComplete. Off, only the transcript and the length are sent.

  • transcribedefault "browser"
    "browser" | "none"

    browser: live words from the Web Speech API where it exists (Chrome, Edge, Safari), restarted when Chrome ends a run; elsewhere the transcript stays empty and the audio still records. none: no live words, for server transcription.

  • langdefault ""
    string

    The recognition language, like "en-US". Empty uses the resolved locale.

  • maxDurationdefault 120
    number

    Seconds. The timer warns 10 seconds before, and at the limit the recording stops, sends itself and announces labels.maxReached. 0 means no limit.

  • minDurationdefault 500
    number

    Milliseconds. A shorter recording is a mis-tap: it is discarded and labels.tooShort shows for a moment.

  • hapticsdefault true
    boolean

    A light tick on start, success on send, warning on cancel and a medium tick when a slide arms cancel. Touch and pen only; a mouse never vibrates a laptop.

  • localedefault ""
    string

    The locale that picks the recognition language when lang is empty. Empty uses the UIProvider's, then the page's.

  • getLevel
    () => number

    A 0 to 1 level read every frame in place of the microphone analyser: a WebRTC call, your text to speech output in voice mode, or a simulation. With getLevel, audio off and transcribe none, nothing opens the microphone at all.

  • onRecordingComplete
    (result: VoiceInputResult) => (void | Promise<unknown>)

    Send, the hold release or the time limit: { blob?, mimeType?, durationMs, transcript? }. Return a promise to keep the processing spinner until it settles; a rejection shows the failed error and Try again sends the same recording again.

  • onTranscript
    (text: string, info: { final: boolean }) => void

    Live words from the browser's recognizer: the whole transcript of this recording so far, final once no words are pending. Dictation into a field uses it.

  • onCancel
    () => void

    Cancel, Escape or a hold released while armed. The recording is gone.

  • labels
    Partial<VoiceInputLabels>

    Every string, English by default (DEFAULT_VOICE_INPUT_LABELS): start, stop, send, cancel, recording, listening, speaking, requesting, processing, denied, unsupported, noMic, tooShort, interrupted, failed, holdHint, tapHint, releaseToSend, slideToCancel, releaseToCancel, retry, dismiss, maxReached and recorded(duration), e.g. (d) => `Recording, ${d}`.

  • classNames
    Partial<Record<"trigger" | "bar" | "cancel" | "dot" | "waveform" | "timer" | "send" | "hint" | "transcript" | "orb" | "status" | "controls" | "error" | "retry", string>>

    Extra classes for the parts: the mic, the pill or bar, Cancel, the recording dot, the waveform, the timer, Send, the hint, the transcript, the orb, its status line and controls row, the error line and Try again.

  • className
    string

    Extra classes for the root, merged last. The root sets the --tone vars, so [--tone:purple] recolors the waveform and Send.

Parts

  • VoiceWaveform

    component

    The bars on their own, fed by any level source: level (a MotionValue), getLevel, or values for the still waveform of a recorded note; bars, active, idleHeight, tone, color. Transforms are written straight to the bars in a rAF loop, never through React state, and pause off screen. Props: VoiceWaveformProps.

  • useVoiceRecorder

    hook

    The engine for custom UIs: { status, start, stop, cancel, retry, dismiss, level, elapsed, transcript, interim, error, supported, metered }. Options mirror the component (status, audio, transcribe, lang, getLevel, minDuration, maxDuration, onComplete, onTranscript, onCancel, onError, onLimit). start only works from a tap or a key press; every track, context and recognizer stops on stop, cancel and unmount, and a second recorder starting stops the first. Signature: (options: UseVoiceRecorderOptions) => VoiceRecorder.

  • DEFAULT_VOICE_INPUT_LABELS

    constant

    The English strings, exported so an app can translate from a complete object. Type: VoiceInputLabels.

Examples

Bar

The full-width bar under a note field; the live transcript sits between them and lands in the note.

Voice mode

The orb in a dark card: it listens, thinks while the promise runs, then blooms while the answer is spoken.

Push to talk

mode="hold" like WhatsApp: release to send, slide away to cancel, with a halo under the finger.

Server transcription

transcribe="none" and onRecordingComplete returning a promise: Transcribing… until the text comes back.

Errors

Blocked microphone with a retry, a browser without voice input, and a tap that was too short.

Tones and sizes

sm, md and lg mics, a brand bar already recording and a small info bar.

Dependencies

motionlucide-reactbuttonmatrix-orbspinnertooltipuse-controllable-stateuse-localeuse-media-queryai-chathapticsmotiontonesui-config

Files

  • components/ui/voice-input.tsx

FAQ

Is the React Voice Input component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Voice Input?

Run npx wingo-ui@latest add voice-input. It also adds the Wingo UI items it builds on (Button, Matrix Orb, Spinner, Tooltip, useControllableState and 7 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/voice-input.

How does Voice Input behave on phones?

On phones every target is 44px, the held mic stops the page from scrolling, and a halo under your finger breathes with your voice.

See also

Prompt InputMatrix OrbFluid OrbButtonAI Chatlib

Used in

Prompt Input

More React AI components

Model PickerAgent Status CardChat MessageChat ThreadChat WidgetConversation List
All 17 React AI components

Articles using this component

  • React Audio Player with Waveform: Scrub, Seek, Peaks

    Component guides · Oct 19, 2026 · 9 min read

  • React Prompt Input: Enter, IME, Files, Stop and Voice

    Building UI with AI agents · Oct 16, 2026 · 10 min read

  • React Speech to Text Input: Mic, Waveform, Fallback

    Building UI with AI agents · Oct 16, 2026 · 9 min read

PreviousUsage MeterNext Workflow Canvas
Northwind AssistantDictation

mode=toggle · audio=true · transcribe=browser · lang=auto · locale=auto · min=500 ms · max=120 s · haptics=true