@nameisjayant/react-native-voice-to-text · v0.1.0

Tap the mic. Say it. Search runs.

Speech-to-text for React Native with a ready-made voice search dialog. It shows the words as they're spoken, closes itself when the user goes quiet, and hands the text to your search.

$ npm install @nameisjayant/react-native-voice-to-text
No API keys platform recognizers TurboModule New Architecture iOS + Android MIT licensed
Restaurants
Search restaurants
PlaceholderPlaceholder
PlaceholderPlaceholder
PlaceholderPlaceholder
Listening…
Speak now
Cancel
status: idlesilence 0.0 s / 2.0 s
How it works

The whole voice search flow, in one component

Uses Android's SpeechRecognizer and iOS's SFSpeechRecognizer, so there are no API keys, servers or extra dependencies.

1

Tap the mic

The user taps the mic inside your search bar. Permission is requested on the spot if needed.

2

Dialog listens

"Listening… Speak now" opens. The ring follows the voice level and the words appear live.

3

Silence stops it

After a short pause with no new words, the session ends and the dialog closes by itself.

4

Search runs

The recognised text lands in your search field and onSearch fires.

Silence auto-stop

Ends the session after a configurable pause. The timing lives in JavaScript, so iOS and Android behave the same.

Every part themeable

Colours, sizes, font, texts, style overrides, or render props that replace the icon or the whole dialog.

Headless hook

useSpeechToText handles permissions, silence detection and cleanup. Bring your own UI.

Any locale

Pass a BCP-47 tag like 'en-US' or 'hi-IN'. Defaults to the device language.

On-device mode

Set onDevice to prefer offline recognition where the platform supports it.

TurboModule

Built for the New Architecture with codegen. Kotlin on Android, Objective-C++ on iOS.

Installation

Two steps on iOS, none on Android

Add the package

terminal
npm install @nameisjayant/react-native-voice-to-text
# or
yarn add @nameisjayant/react-native-voice-to-text

cd ios && pod install
Use a real device. Speech recognition needs a recognition service (the Google app on Android). Most emulators and simulators don't have a working microphone or recognizer.

iOS: add usage descriptions

Both keys go in Info.plist. iOS shows these strings in the permission prompts.

ios/YourApp/Info.plist
<key>NSMicrophoneUsageDescription</key>
<string>The microphone is used to search by voice.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Speech recognition converts what you say into search text.</string>

Android: nothing to do

The library's manifest adds RECORD_AUDIO and the RecognitionService package-visibility query. The runtime permission prompt appears when listening starts.

Usage

Pick how much the library does for you

Four entry points, from a drop-in search bar down to the raw native module. Each one is built on the next.

A complete search field. Keyboard submit and voice results both call onSearch.

SearchScreen.tsx
import { VoiceSearchBar } from '@nameisjayant/react-native-voice-to-text';

const [query, setQuery] = useState('');

<VoiceSearchBar
  value={query}
  onChangeText={setQuery}
  onSearch={(text) => runSearch(text)} // keyboard submit or voice result
  placeholder="Search restaurants"
/>;
Behaviour

When does listening stop?

Three timers decide. They work on VoiceSearchDialog, the search bar's dialogProps, and useSpeechToText.

0 s · mic opensfirst wordlast wordstops
initialSilenceTimeout6,000 msHow long to wait for the user to start speaking.
silenceTimeout2,000 msTime without new words, after speech started, before it stops.
maxDuration60,000 msHard cap on one session. 0 turns it off.
idle→ starting→ listening→ processing→ idle or error

Tapping the mic while listening finishes immediately. Tapping it after an error or no-speech starts a retry.

Customising the UI

Restyle it, translate it, or replace it

Theme values and texts cover most apps. Style props and render props cover the rest.

Theme, position and texts

theme + texts
<VoiceSearchDialog
  visible={open}
  onResult={setQuery}
  onClose={() => setOpen(false)}
  position="bottom"          // 'center' (default) | 'bottom' sheet
  animationType="slide"      // 'fade' (default) | 'slide' | 'none'
  theme={{
    backgroundColor: '#14112B',
    primaryColor: '#6C4DFF',
    pulseColor: 'rgba(108, 77, 255, 0.3)',
    borderRadius: 28,
    micSize: 84,
    fontFamily: 'Inter-Regular',
  }}
  texts={{ listening: 'Escuchando…', speakNow: 'Habla ahora', cancel: 'Cancelar' }}
/>

defaultVoiceSearchTheme (dark) and lightVoiceSearchTheme are exported, so you can spread one and change a few values.

Replace parts of the dialog

render props
// Your own icon inside the mic circle
<VoiceSearchDialog
  renderMicIcon={({ size, color }) => <Icon name="mic" size={size} color={color} />}
/>

// Your own dialog content; the library still
// handles listening and silence detection
<VoiceSearchDialog
  renderContent={({ status, transcript, volume, stop, retry, cancel }) => (
    <MyListeningCard
      status={status}
      text={transcript}
      level={volume}
      onStop={stop}
      onCancel={cancel}
    />
  )}
/>

renderContent also receives error, noSpeech, theme and texts.

Reference

Props, theme keys and texts

Behaviour options

On VoiceSearchDialog, dialogProps and useSpeechToText.

PropDefaultDescription
silenceTimeout2000ms without new words (after speech started) before it stops
initialSilenceTimeout6000ms to wait for the user to start speaking
maxDuration60000Hard cap on a session (0 = off)
localedeviceBCP-47 tag, e.g. 'en-US', 'hi-IN'
onDevicefalsePrefer offline recognition where supported
closeOnNoSpeechtrueDialog only. If false, shows "Didn't catch that" with a retry instead of closing
dismissOnBackdropPresstrueDialog only
onPartialResult, onError–Callbacks

Theme keys

Values shown for defaultVoiceSearchTheme and lightVoiceSearchTheme.

KeyDefault (dark)LightUsed for

Texts

Pass any subset to texts. defaultVoiceSearchTexts is exported.

KeyDefault
startingGetting ready…
listeningListening…
speakNowSpeak now
processingGot it
noSpeechDidn't catch that
tapToRetryTap the mic to try again
errorSomething went wrong
permissionDeniedMicrophone permission is required
cancelCancel

Dialog style overrides

backdropStylecardStylemicContainerStyletitleStylesubtitleStyletranscriptStylecancelButtonStylecancelTextStyle

VoiceSearchBar

Takes every TextInput prop, plus:

containerStyleinputStyleshowSearchIconsearchIconColorrenderSearchIconmicButtonPropsrenderMicButton={({ open }) => …}fillOnlydialogProps

fillOnly fills the field without calling onSearch. dialogProps accepts anything VoiceSearchDialog does.

MicButton

Every Pressable prop, plus:

size = 40iconColor = '#5F6368'backgroundColorpressedOpacity = 0.6styleicon

Error codes

error.code on onError and the hook's error.

permission_deniedunavailablestart_failedno_matchspeech_timeoutaudioclientnetworknetwork_timeoutbusyserverrecognition_failedunknown

Exports

VoiceSearchBarVoiceSearchDialogMicButtonMicIconSearchIconuseSpeechToTextSpeechToTextdefaultVoiceSearchThemelightVoiceSearchThemedefaultVoiceSearchTexts
Platform notes

Good to know before you ship

AndroidThe system recognizer also ends sessions on its own silence detection, which can be shorter than a long silenceTimeout. The timeout is passed as a hint, but some devices ignore it.
iOSiOS has no built-in endpointing. The silenceTimeout is what ends the session.
BothOnly one recognition session can run at a time. Starting a new one cancels the previous.
The project

Tested, typed, and ready to try

Written in TypeScript with Jest 30 and React Native Testing Library covering the native wrapper, the hook, and every component. The repo includes an example app.

Version
0.1.0
React Native
New Architecture
Android
minSdk 24 · Kotlin
iOS
Objective-C++

Run the example app

terminal
yarn
yarn example android
yarn example ios

Run the tests

terminal
yarn test        # Jest unit tests
yarn typecheck   # tsc
yarn lint        # eslint