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.
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.
Tap the mic
The user taps the mic inside your search bar. Permission is requested on the spot if needed.
Dialog listens
"Listening… Speak now" opens. The ring follows the voice level and the words appear live.
Silence stops it
After a short pause with no new words, the session ends and the dialog closes by itself.
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.
Two steps on iOS, none on Android
Add the package
npm install @nameisjayant/react-native-voice-to-text # or yarn add @nameisjayant/react-native-voice-to-text cd ios && pod install
iOS: add usage descriptions
Both keys go in Info.plist. iOS shows these strings in the permission prompts.
<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.
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.
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"
/>;Keep your own search bar. Drop in MicButton and open the dialog yourself.
import { MicButton, VoiceSearchDialog } from '@nameisjayant/react-native-voice-to-text';
const [open, setOpen] = useState(false);
<View style={styles.searchBar}>
<TextInput value={query} onChangeText={setQuery} />
<MicButton onPress={() => setOpen(true)} />
</View>
<VoiceSearchDialog
visible={open}
onResult={(text) => {
setQuery(text);
runSearch(text);
}}
onClose={() => setOpen(false)}
/>Build any UI you like. The hook handles permissions, silence detection and cleanup. It also returns isListening.
import { useSpeechToText } from '@nameisjayant/react-native-voice-to-text';
const { status, transcript, volume, error, start, stop, cancel } = useSpeechToText({
locale: 'en-US',
silenceTimeout: 1500,
onResult: (text) => setQuery(text),
});
// status: 'idle' | 'starting' | 'listening' | 'processing' | 'error'
// volume: mic level 0..1 while listeningDirect access to the native recognizer. Only one session runs at a time; starting a new one cancels the previous.
import { SpeechToText } from '@nameisjayant/react-native-voice-to-text';
await SpeechToText.isAvailable();
await SpeechToText.requestPermissions();
const sub = SpeechToText.onResult(({ value, isFinal }) => {});
await SpeechToText.start({ locale: 'en-US' });
await SpeechToText.stop(); // final result follows
await SpeechToText.cancel(); // no result
sub.remove();onResultonErroronVolumeChange (0..1)onStartonEndWhen does listening stop?
Three timers decide. They work on VoiceSearchDialog, the search bar's dialogProps, and useSpeechToText.
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.Tapping the mic while listening finishes immediately. Tapping it after an error or no-speech starts a retry.
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
<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
// 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.
Props, theme keys and texts
Behaviour options
On VoiceSearchDialog, dialogProps and useSpeechToText.
| Prop | Default | Description |
|---|---|---|
silenceTimeout | 2000 | ms without new words (after speech started) before it stops |
initialSilenceTimeout | 6000 | ms to wait for the user to start speaking |
maxDuration | 60000 | Hard cap on a session (0 = off) |
locale | device | BCP-47 tag, e.g. 'en-US', 'hi-IN' |
onDevice | false | Prefer offline recognition where supported |
closeOnNoSpeech | true | Dialog only. If false, shows "Didn't catch that" with a retry instead of closing |
dismissOnBackdropPress | true | Dialog only |
onPartialResult, onError | – | Callbacks |
Theme keys
Values shown for defaultVoiceSearchTheme and lightVoiceSearchTheme.
| Key | Default (dark) | Light | Used for |
|---|
Texts
Pass any subset to texts. defaultVoiceSearchTexts is exported.
| Key | Default |
|---|---|
starting | Getting ready… |
listening | Listening… |
speakNow | Speak now |
processing | Got it |
noSpeech | Didn't catch that |
tapToRetry | Tap the mic to try again |
error | Something went wrong |
permissionDenied | Microphone permission is required |
cancel | Cancel |
Dialog style overrides
backdropStylecardStylemicContainerStyletitleStylesubtitleStyletranscriptStylecancelButtonStylecancelTextStyleVoiceSearchBar
Takes every TextInput prop, plus:
containerStyleinputStyleshowSearchIconsearchIconColorrenderSearchIconmicButtonPropsrenderMicButton={({ open }) => …}fillOnlydialogPropsfillOnly fills the field without calling onSearch. dialogProps accepts anything VoiceSearchDialog does.
MicButton
Every Pressable prop, plus:
size = 40iconColor = '#5F6368'backgroundColorpressedOpacity = 0.6styleiconError codes
error.code on onError and the hook's error.
permission_deniedunavailablestart_failedno_matchspeech_timeoutaudioclientnetworknetwork_timeoutbusyserverrecognition_failedunknownExports
VoiceSearchBarVoiceSearchDialogMicButtonMicIconSearchIconuseSpeechToTextSpeechToTextdefaultVoiceSearchThemelightVoiceSearchThemedefaultVoiceSearchTextsGood to know before you ship
silenceTimeout. The timeout is passed as a hint, but some devices ignore it.silenceTimeout is what ends the session.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.
Run the example app
yarn yarn example android yarn example ios
Run the tests
yarn test # Jest unit tests yarn typecheck # tsc yarn lint # eslint