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

@syntropy-labs/react-web-speech

v0.1.2

Published

A React library for the Web Speech API with first-class DX: mic permissions, listening states, browser compatibility, and cursor-aware text insertion.

Readme

@syntropy-labs/react-web-speech

React hooks for the Web Speech API with first-class DX: mic permissions, listening states, browser compatibility, and cursor-aware text insertion.

npm version License: MIT Open in StackBlitz

Features

  • 🎙️ Mic permission state management — Know if permission is prompt, granted, or denied
  • 📝 Cursor-aware text insertion — Insert transcribed text at cursor position
  • 🔇 Auto-silence detection — Automatically stop listening after silence
  • 🔄 Auto-restart on network errors — Resilient to connection issues
  • 🌐 Browser compatibility — Handles Chrome, Edge, Safari with proper prefixing
  • 📦 Tree-shakeable — Only bundle what you use (~5KB gzipped)
  • 🔷 TypeScript-first — Full type safety and IDE autocomplete
  • ⚛️ React 17+ ready — Strict Mode compatible

Installation

npm install @syntropy-labs/react-web-speech
# or
yarn add @syntropy-labs/react-web-speech
# or
pnpm add @syntropy-labs/react-web-speech

Quick Start

import { useSpeechInput } from '@syntropy-labs/react-web-speech'

function VoiceInput() {
  const {
    transcript,
    isListening,
    isSupported,
    permissionState,
    start,
    stop,
    toggle,
  } = useSpeechInput({
    lang: 'en-US',
    continuous: false,
    silenceTimeout: 3000,
  })

  if (!isSupported) {
    return <p>Speech recognition is not supported in this browser.</p>
  }

  return (
    <div>
      <button onClick={toggle}>
        {isListening ? '🔴 Stop' : '🎙️ Start'}
      </button>
      <p>Permission: {permissionState}</p>
      <p>Transcript: {transcript}</p>
    </div>
  )
}

API Reference

useSpeechInput(options?)

The primary hook for speech-to-text functionality.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | lang | string | navigator.language | Recognition language (e.g., 'en-US') | | continuous | boolean | false | Keep listening after pause | | interimResults | boolean | true | Show real-time partial results | | maxAlternatives | number | 1 | Max alternative transcriptions | | silenceTimeout | number | 3000 | Auto-stop after silence (ms), 0 to disable | | autoRestart | boolean | false | Auto-restart on network errors | | onResult | (text, isFinal) => void | - | Callback on speech result | | onError | (error) => void | - | Callback on error | | onStart | () => void | - | Callback when listening starts | | onEnd | () => void | - | Callback when listening ends |

Returns

| Property | Type | Description | |----------|------|-------------| | transcript | string | Final transcribed text | | interimTranscript | string | Real-time partial text | | isListening | boolean | Currently listening | | isSupported | boolean | Browser supports Speech API | | permissionState | 'prompt' \| 'granted' \| 'denied' \| 'unsupported' | Mic permission state | | error | SpeechError \| null | Error details | | start | () => Promise<void> | Start listening | | stop | () => void | Stop listening gracefully | | abort | () => void | Abort listening immediately | | toggle | () => Promise<void> | Toggle listening | | clear | () => void | Clear transcript and error | | requestPermission | () => Promise<MicPermissionState> | Request mic permission |


useSpeechInputWithCursor(options)

Extended hook that automatically inserts transcribed text at the cursor position.

import { useSpeechInputWithCursor } from '@syntropy-labs/react-web-speech'
import { useState, useRef } from 'react'

function VoiceTextarea() {
  const [value, setValue] = useState('')
  const inputRef = useRef<HTMLTextAreaElement>(null)

  const { isListening, toggle } = useSpeechInputWithCursor({
    inputRef,
    value,
    onChange: setValue,
    appendSpace: true, // Add space after inserted text
  })

  return (
    <div>
      <textarea ref={inputRef} value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={toggle}>{isListening ? 'Stop' : 'Speak'}</button>
    </div>
  )
}

Additional Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | inputRef | RefObject<HTMLInputElement \| HTMLTextAreaElement> | required | Ref to the input element | | value | string | required | Current controlled value | | onChange | (value: string) => void | required | Value setter | | appendSpace | boolean | true | Add space after inserted text |

Additional Returns

| Property | Type | Description | |----------|------|-------------| | insertAtCursor | (text: string) => void | Manually insert text at cursor |


Cursor Utilities

Low-level utilities for cursor position management:

import { 
  supportsSelection,
  getCursorPosition,
  setCursorPosition,
  insertTextAtCursor 
} from '@syntropy-labs/react-web-speech'

// Check if input type supports cursor APIs
supportsSelection(inputElement) // true for text, search, tel, password, url

// Get current cursor position
const { start, end } = getCursorPosition(inputElement)

// Set cursor position (uses requestAnimationFrame for React compatibility)
setCursorPosition(inputElement, position, { focus: true })

// Insert text at cursor in controlled input
insertTextAtCursor(inputRef, 'hello', currentValue, setValue)

Note: email and number input types don't support cursor APIs. The utilities fall back to appending text at the end.


Browser Support

| Browser | Support | |---------|---------| | Chrome / Chromium | ✅ Full | | Edge | ✅ Full | | Safari 14.1+ | ⚠️ Partial (webkit prefix) | | Firefox | ❌ Not supported |

Note: The Web Speech API requires HTTPS in production (except localhost).


SSR / Next.js

This package is SSR-safe. The Web Speech API is only accessed on the client.

Next.js App Router

'use client'

import { useSpeechInput } from '@syntropy-labs/react-web-speech'

export function VoiceButton() {
  const { isListening, toggle, isSupported } = useSpeechInput()
  
  if (!isSupported) return null
  
  return (
    <button onClick={toggle}>
      {isListening ? 'Stop' : 'Speak'}
    </button>
  )
}

Next.js Pages Router

import dynamic from 'next/dynamic'

const VoiceInput = dynamic(
  () => import('../components/VoiceInput'),
  { ssr: false }
)

TypeScript

All types are exported:

import type {
  UseSpeechInputOptions,
  UseSpeechInputReturn,
  UseSpeechInputWithCursorOptions,
  UseSpeechInputWithCursorReturn,
  SpeechError,
  SpeechErrorType,
  MicPermissionState,
  CursorPosition,
  BrowserCapabilities,
} from '@syntropy-labs/react-web-speech'

Advanced Examples

Voice-controlled Form

import { useState, useRef } from 'react'
import { useSpeechInputWithCursor } from '@syntropy-labs/react-web-speech'

function VoiceForm() {
  const [formData, setFormData] = useState({ name: '', email: '' })
  const [activeField, setActiveField] = useState<'name' | 'email'>('name')
  const inputRefs = {
    name: useRef<HTMLInputElement>(null),
    email: useRef<HTMLInputElement>(null),
  }

  const { toggle, isListening } = useSpeechInputWithCursor({
    inputRef: inputRefs[activeField],
    value: formData[activeField],
    onChange: (value) => setFormData({ ...formData, [activeField]: value }),
  })

  return (
    <form>
      <input
        ref={inputRefs.name}
        value={formData.name}
        onFocus={() => setActiveField('name')}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        placeholder="Name"
      />
      <input
        ref={inputRefs.email}
        value={formData.email}
        onFocus={() => setActiveField('email')}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        placeholder="Email"
      />
      <button type="button" onClick={toggle}>
        {isListening ? '🔴 Stop' : '🎙️ Speak'}
      </button>
    </form>
  )
}

Real-time Transcript Display

import { useSpeechInput } from '@syntropy-labs/react-web-speech'

function LiveTranscript() {
  const { transcript, interimTranscript, isListening, toggle } = useSpeechInput({
    interimResults: true,
    continuous: true,
  })

  return (
    <div>
      <button onClick={toggle}>{isListening ? 'Stop' : 'Start'}</button>
      <p>
        {transcript}
        <span style={{ opacity: 0.5 }}>{interimTranscript}</span>
      </p>
    </div>
  )
}

Troubleshooting

"Permission denied" error

The user has denied microphone access. They need to:

  1. Click the 🔒 icon in the browser address bar
  2. Reset microphone permissions
  3. Refresh the page

"Network error"

The Web Speech API requires an internet connection. Chrome sends audio to Google's servers for processing.

Recognition stops immediately

Some browsers stop recognition after detecting silence. Solutions:

  • Use continuous: true for longer sessions
  • Increase silenceTimeout (or set to 0 to disable)

Works in development but not production

The Web Speech API requires HTTPS. Make sure your production site uses SSL.

Duplicate React error with yarn link

When testing locally with yarn link, add React aliases to your Vite config:

// vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  resolve: {
    alias: {
      react: path.resolve('./node_modules/react'),
      'react-dom': path.resolve('./node_modules/react-dom'),
    },
  },
})

Why This Package?

Existing React speech-to-text packages lack critical production-ready features:

| Feature | Other Packages | This Package | |---------|---------------|--------------| | Mic permission state | ❌ | ✅ | | Insert text at cursor | ❌ | ✅ | | Auto-silence detection | ❌ | ✅ | | Auto-restart on errors | ❌ | ✅ | | TypeScript-first | Varies | ✅ | | React 18 Strict Mode | ❌ | ✅ |

Contributing

Contributions are welcome! Please read our Contributing Guide first.

# Clone the repo
git clone https://github.com/SyntropyLabs/react-web-speech.git
cd react-web-speech

# Install dependencies
yarn install

# Run tests
yarn test

# Type check
yarn typecheck

# Build
yarn build

License

MIT © SyntropyLabs