react-native-tts-kit: On-Device Neural TTS in Three Lines
Ali Dulaimi
Here is the entire hello-world:
import TTSKit from 'react-native-tts-kit'
await TTSKit.speak('Hello, world.')
That's neural text-to-speech — the kind that sounds like a person, not a navigation system — running entirely on the phone. No API key. No network calls after the one-time model download. No per-request bill. 31 languages. Flowent is built around a real-time voice agent, and React Native had no good answer for on-device neural TTS — so we built one, and today we're open-sourcing it as react-native-tts-kit.
One thing up front, because this blog doesn't oversell: it's a v0.1 alpha, verified on exactly two devices, and mid-range Android performance is currently bad. The specifics are below, with numbers. The rest of this post walks the path you'd actually walk — install, first speak(), then the three things you have to deal with before shipping.
Don't want to take my word on the voice quality? The same model runs live in your browser — or jump to the demo embedded in this post and hear it without installing anything.
Why this needed to exist
| Option | Quality | Offline | Cost | Languages |
|---|---|---|---|---|
expo-speech (system) | Robotic | Yes | Free | OS-bound |
| ElevenLabs / OpenAI TTS | Excellent | No | Per-request | 30+ |
react-native-tts-kit | Neural | Yes | Free | 31 |
Every existing option had a dealbreaker for us. System TTS via expo-speech is free and offline, but it sounds robotic in a way users immediately clock — and for a language app whose whole job is modelling how a language sounds, that's disqualifying. Cloud TTS sounds excellent, but you pay per request, synthesis dies the moment the network does, and every sentence your user reads gets shipped to a third-party server. That's a bill, a failure mode, and a privacy leak, bundled together.
On-device neural TTS removes all three at once. The tradeoff it introduces — a 210 MB model download — is real, and gets its own section below.
Install
npx expo install react-native-tts-kit
npx expo prebuild --platform ios
cd ios && pod install && cd ..
npx expo run:ios --device
Two notes before you run that:
- Expo Go won't work. This is custom native code, so you need a dev build — that's what the
prebuildandrun:iossteps are for. If you've shipped anything native-adjacent with Expo in 2026, this is the workflow you already know. - Bare React Native works too — same flow, just add
expo-modules-coreas a peer dependency.
First speak()
The default call uses the F1 voice and English. The more interesting property is that all 10 voices (M1 through M5, F1 through F5) are language-agnostic — any voice can speak any of the 31 languages:
await TTSKit.speak('Bonjour le monde', { voice: 'F1', language: 'fr' })
await TTSKit.speak('こんにちは', { voice: 'M2', language: 'ja' })
await TTSKit.speak('안녕하세요', { voice: 'F3', language: 'ko' })
The 31 languages cover most of Europe plus Arabic, Hindi, Indonesian, Japanese, Korean, Turkish, Ukrainian, and Vietnamese — all verified to produce intelligible neural-quality audio on iPhone. The full list is exported as SUPERTONIC_LANGUAGES, and the example app in the repo ships a tappable sample sentence for every single one, so you can judge the quality with your own ears before committing to anything. One honest gap: no Chinese — it isn't in Supertonic-3's open-source weights. If you need it, cloud engine adapters are planned for v1.2.
For long text, don't wait for the whole synthesis — stream it, and first audio arrives before synthesis finishes:
const stream = TTSKit.stream(longArticle, { language: 'en' })
stream.on('chunk', (pcm) => {
/* PCM16LE @ 44.1 kHz */
})
stream.on('end', () => {})
// and you can bail out of in-flight synthesis:
await TTSKit.stop()
That's the whole API surface you need day to day. Now the shipping concerns.
Shipping concern 1: the 210 MB elephant
This is the price of on-device neural quality: the multilingual model is ~210 MB in fp16 — one file, vector_estimator.onnx, is 138 MB on its own because it carries the cross-lingual weights for all 31 languages. It downloads from HuggingFace on first use, is verified against SHA-256 fingerprints when the package ships them, and lands in app-private storage. After that, every call is instant and offline.
Do not let that download ambush a user mid-conversation. Trigger it from a settings or onboarding screen with a progress bar:
await TTSKit.prefetchModel((p) => {
setProgress(p.percent) // 0–100
})
If even a one-time download is unacceptable, you can bundle the model with your app assets and ship fully offline from the first launch. And if 210 MB is simply too much for your app, the honest answer is that this library may not be for you — decide whether that trade fits before you adopt it. The system engine is still there as an explicit escape hatch:
TTSKit.setEngine('system') // robotic, but zero download
Shipping concern 2: performance, the honest version
| Metric | iPhone (iOS 26.4, Debug build) |
|---|---|
| Time to first audio (one sentence) | ~70 ms |
| Real-time factor | under 0.5x |
| Cold start (first speak after launch) | ~1–2 s |
| Peak memory during synthesis | ~250 MB |
Now the caveat, because it matters: those numbers are from one iPhone. The library is verified on two devices total — an iPhone on iOS 26+ and a Samsung A52 on Android 14. On the iPhone, synthesis is sub-second. On the A52, it currently takes about 8 seconds — fine for pre-generating audio ahead of playback, not for live interaction. We expect flagship Android (S22+, Pixel 8+) to land in the 2–3 second range, but that is a projection, not a measurement — "expect" is doing real work in that sentence. Benchmarks across more devices ship with v1.0, and there's a reproducible harness in benchmarks/ if you'd rather measure than trust. If you're Android-first, wait for v1.0 or test on your own hardware before committing.
That's what v0.1 alpha means here: the pipeline runs end to end, but it's been validated on one iPhone, and you should expect rough edges for a couple of weeks post-launch.
Shipping concern 3: the license homework
The package itself is MIT. The Supertonic-3 model weights (99M parameters, by Supertone) are BigScience OpenRAIL-M, and if you ship them in your app, the license puts three obligations on you:
- Bind your end users to the Use Restrictions — most importantly, no impersonation or deepfakes without consent, and AI-generated audio must be disclosed as such.
- Ship a copy of
licenses/OpenRAIL-M.txtwith your app. - Preserve the Supertone copyright notice.
This sounds heavier than it is: a two-line clause in your ToS covers it, and the repo's ATTRIBUTIONS.md has copy-paste boilerplate. But it's a real obligation, not a formality — and given what voice synthesis can be misused for, these are restrictions worth having.
Privacy, since we're here
This is the part cloud TTS structurally cannot offer. Once the model is downloaded, text passed to speak() or stream() never leaves the device — synthesis runs in a local ONNX session (four of them, actually: duration predictor, text encoder, vector estimator, vocoder) and audio plays through the platform audio engine. The only network activity in the library's entire life is that initial model fetch. If your users are typing journal entries, medical notes, or anything else you'd rather not forward to a third-party TTS endpoint, this is the whole argument in one paragraph.
Worth noting honestly: this is not the only attempt at the problem. expo-kokoro-onnx exists and is a valid choice — different model (Kokoro-82M vs Supertonic-3), fewer languages (8 vs 31), slower time-to-first-audio, and an espeak-ng dependency we wanted to avoid. We picked Supertonic for Flowent's needs; you should pick for yours.
Try it in your browser
This is the exact pipeline described above — the same four Supertonic-3 ONNX sessions — running on this page via ONNX Runtime Web (WebGPU when your browser has it, WebAssembly otherwise). One click downloads the model, your browser caches it, and every synthesis after that happens locally: the text you type never leaves this page.
The multilingual Supertonic-3 model is a one-time 213 MB download from HuggingFace, cached by your browser. Synthesis then runs entirely on this page — your text never leaves it.
Where this goes
v1.0 is Android validation and benchmarks across four-plus devices. v1.1 adds voice cloning via NeuTTS Air — a 3-second sample becomes a cloned voice. v1.2 puts cloud engines (ElevenLabs, OpenAI, Cartesia) behind the same API. v2.0 targets the browser via WASM/WebGPU.
None of that matters if the core isn't solid, and the fastest way to make it solid is more devices and more ears. Android performance reports from real hardware are the single most valuable feedback we can get right now. If your React Native app speaks — or should —
npx expo install react-native-tts-kit
and tell us what breaks via GitHub issues. There's a demo video if you want to hear it before installing anything — or better, try it live in your browser: the same Supertonic-3 model running on this site via ONNX Runtime Web, no install required.
Want to discuss this? DM me on LinkedIn or drop me an email.
Performance numbers are from a single-device run (iPhone, iOS 26.4, Debug build); the reproducible harness lives in benchmarks/.