React hook and component for converting text to speech using the Web Speech API or Amazon Polly.
npm install tts-reacttts-react!CI



tts-react provides a hook (useTts) and component (TextToSpeech) to convert text to speech. In most cases you want the hook so you can use custom styling on the audio controls.
By default tts-react uses the SpeechSynthesis and SpeechSynthesisUtterance API's. You can fallback to the HTMLAudioElement API by providing a fetchAudioData prop to the hook or component.
``console`
npm i react react-dom tts-react
For projects using React 18:
`console`
npm i react@18 react-dom@18 tts-react@3
#### ESM + CDN
Get up and running quickly using tts-react with ESM from a CDN. This example uses React 19 with htm:
`html`
#### Hook
You can use the hook to create a Speak component that converts the text to speech on render:
`tsx
import { useTts } from 'tts-react'
import type { TTSHookProps } from 'tts-react'
type SpeakProps = Pick
const Speak = ({ children }: SpeakProps) => (
<>{useTts({ children, autoPlay: true }).ttsChildren}>
)
const App = () => { This text will be spoken on render.
return (
)
}
`
Or create a more advanced component with controls for adjusting the speaking:
`tsx
import { useTts } from 'tts-react'
import type { TTSHookProps } from 'tts-react'
interface CustomProps extends TTSHookProps {
highlight?: boolean
}
const CustomTTSComponent = ({ children, highlight = false }: CustomProps) => {
const { ttsChildren, state, play, stop, pause } = useTts({
children,
markTextAsSpoken: highlight
})
return (
const App = () => { Some text to be spoken and highlighted.
return (
)
}
`
#### Component
Use the TextToSpeech component for an out of the box solution:
`tsx
import { TextToSpeech, Positions, Sizes } from 'tts-react'
const App = () => {
return (
align="vertical"
size={Sizes.SMALL}
position={Positions.TL}>
Some text to be spoken.
useTtsThe hook returns the internal state of the audio being spoken, getters/setters of audio attributes, callbacks that can be used to control playing/stopping/pausing/etc. of the audio, and modified
children if using markTextAsSpoken. The parameters accepted are described in the Props section. The response object is described by the TTSHookResponse type.`ts
const {
get,
set,
state,
spokenText,
ttsChildren,
play,
stop,
pause,
replay,
playOrPause,
playOrStop,
toggleMute
} = useTts({
lang,
voice,
children,
autoPlay,
markTextAsSpoken,
markColor,
markBackgroundColor,
onStart,
onBoundary,
onPause,
onEnd,
onError,
onVolumeChange,
onPitchChange,
onRateChange,
fetchAudioData
})interface TTSHookProps extends MarkStyles {
/* The spoken text is extracted from here. /
children: ReactNode
/* The
SpeechSynthesisUtterance.lang to use. /
lang?: string
/* The SpeechSynthesisUtterance.voice to use. /
voice?: SpeechSynthesisVoice
/* The initial rate of the speaking audio. /
rate?: number
/* The initial volume of the speaking audio. /
volume?: number
/* Whether the text should be spoken automatically, i.e. on render. /
autoPlay?: boolean
/* Whether the spoken word should be wrapped in a element. /
markTextAsSpoken?: boolean
/* Callback when the volume is changed. /
onVolumeChange?: (newVolume: number) => void
/* Callback when the rate is changed. /
onRateChange?: (newRate: number) => void
/* Callback when the pitch is changed. /
onPitchChange?: (newPitch: number) => void
/* Callback when there is an error of any kind. /
onError?: (msg: string) => void
/* Callback when speaking/audio starts playing. /
onStart?: (evt: SpeechSynthesisEvent | Event) => void
/* Callback when the speaking/audio is paused. /
onPause?: (evt: SpeechSynthesisEvent | Event) => void
/* Calback when the current utterance/audio has ended. /
onEnd?: (evt: SpeechSynthesisEvent | Event) => void
/* Function to call when the SpeechSynthesis API is not supported. /
onNotSupported?: TTSFallback
/* Callback when a word boundary/mark has been reached. /
onBoundary?: (evt: SpeechSynthesisEvent | Event) => void
/* Function to fetch audio and speech marks for the spoken text. /
fetchAudioData?: (spokenText: string) => Promise
}
interface TTSHookResponse {
set: {
lang: (value: string) => void
rate: (value: number) => void
pitch: (value: number) => void
volume: (value: number) => void
preservesPitch: (value: boolean) => void
}
get: {
lang: () => string
rate: () => number
pitch: () => number
volume: () => number
preservesPitch: () => boolean
}
/* Whether speechSynthesis is supported. /
isSynthSupported: boolean
/* State of the current speaking/audio. /
state: TTSHookState
/* The text extracted from the children elements and used to synthesize speech. /
spokenText: string
play: () => void
stop: () => void
pause: () => void
replay: () => void
/* Toggles between muted/unmuted, i.e. volume is zero or non-zero. /
toggleMute: (callback?: (wasMuted: boolean) => void) => void
/* Toggles between play/stop. /
playOrStop: () => void
/* Toggles between play/pause. /
playOrPause: () => void
/* The original children with a possible included if using markTextAsSpoken. /
ttsChildren: ReactNode
}
interface TTSHookState {
voices: SpeechSynthesisVoice[]
boundary: BoundaryUpdate
isPlaying: boolean
isPaused: boolean
isMuted: boolean
isError: boolean
isReady: boolean
}
interface TTSBoundaryUpdate {
word: string
startChar: number
endChar: number
}
`fetchAudioDataUsing
fetchAudioData will bypass SpeechSynthesis and use the HTMLAudioElement.`ts
;(spokenText: string) => Promise
`When using
fetchAudioData it must return TTSAudioData which has the following shape:`ts
interface PollySpeechMark {
end: number
start: number
time: number
type: 'word'
value: string
}
interface TTSAudioData {
audio: string
marks?: PollySpeechMark[]
}
`The
audio property must be a URL that can be applied to HTMLAudioElement.src, including a data URL. If using markTextAsSpoken then you must also return the marks that describe the word boundaries. PollySpeechMarks have the same shape as the Speech Marks used by Amazon Polly, with the restriction that they must be of type: 'word'.Props
Most of these are supported by the
useTts hook, but those marked with an asterisk are exclusive to the TextToSpeech component.
* Only applies to TextToSpeech component.| Name | Required | Type | Default | Description |
| ------------------------------ | -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| children | yes |
ReactNode | none | Provides the text that will be spoken. |
| lang | no | string | The one used by SpeechSynthesisUtterance.lang. | Sets the SpeechSynthesisUtterance.lang. Overrides voice when set and voice.lang does not match lang. |
| voice | no | SpeechSynthesisVoice | None or the voice provided by audio from TTSAudioData. | The voice heard when the text is spoken. Calling set.lang may override this value. |
| autoPlay | no | boolean | false | Whether the audio of the text should automatically be spoken when ready. |
| markTextAsSpoken | no | boolean | false | Whether the word being spoken should be highlighted. |
| markColor | no | string | none | Color of the text that is currently being spoken. Only applies with markTextAsSpoken. |
| markBackgroundColor | no | string | none | Background color of the text that is currently being spoken. Only applies with markTextAsSpoken. |
| fetchAudioData | no | (text: string) => Promise | none | Function to return the optional SpeechMarks[] and audio URL for the text to be spoken. See fetchAudioData for more details. |
| *allowMuting | no | boolean | true | Whether an additional button will be shown on the component that allows muting the audio. |
| *onMuteToggled | no | (wasMuted: boolean) => void | none | Callback when the user clicks the mute button shown from allowMuting being enabled. Can be used to toggle global or local state like whether autoPlay should be enabled. |
| onStart | no | (evt: SpeechSynthesisEvent \| Event) => void | none | Callback when the speaking/audio has started (or resumed) playing. |
| onPause | no | (evt: SpeechSynthesisEvent \| Event) => void | none | Callback when the speaking/audio has been paused. |
| onEnd | no | (evt: SpeechSynthesisEvent \| Event) => void | none | Callback when the speaking/audio has stopped. |
| onBoundary | no | (boundary: TTSBoundaryUpdate, evt: SpeechSynthesisEvent \| Event) => void | none | Callback when a word boundary/mark has been reached. |
| onError | no | (msg: string) => void | none | Callback when there is an error of any kind playing the spoken text. The error message (if any) will be provided. |
| onNotSupported | no | () => void | none | Callback when SpeechSynthesis is not supported. |
| onVolumeChange | no | (newVolume: number) => void | none | Callback when the volume has changed. |
| onRateChange | no | (newRate: number) => void | none | Callback when the rate has changed. |
| onPitchChange | no | (newPitch: number) => void | none | Callback when the pitch has changed. |
| *align | no | 'horizontal' \| 'vertical' | 'horizontal' | How to align the controls within the TextToSpeech component. |
| *size | no | 'small' \| 'medium' \| 'large' | 'medium' | The relative size of the controls within the TextToSpeech component. |
| *position | no | 'topRight' \| 'topLeft' \| 'bottomRight' \| 'bottomLeft' | 'topRight' | The relative positioning of the controls within the TextToSpeech component. |
| *useStopOverPause | no | boolean | false | Whether the controls should display a stop button instead of a pause button. On Android devices, SpeechSynthesis.pause() behaves like cancel()`, so you can use this prop in that context. |This is independent of Why does speaking sometimes stop prematurely around 255 characters?
tts-react and will happen if you are using the SpeechSynthesis Web Speech API on a platform that only provides network-based SpeechSynthesisVoices. See the associated issue for more details. Unfortunately, all you can really do is try another platform that has local voices installed.
Due to the way The traversal does not go deeper than React elements: they don't get rendered, and their children aren't traversed.Why is text inside child components not being spoken?
Children.map workstts-react can not extract the text from child components. Instead, include the text as a direct child of TextToSpeech (or useTts).
The Why does
markTextAsSpoken sometimes highlight the wrong word?SpeechSynthesisUtterance boundary event may fire with skewed word boundaries for certain combinations of spokenText and lang or voice props. If you check the value of state.boundary.word in these cases, you will find the event is firing at unexpected boundaries, so there is no real solution other than to find a suitable voice for your given spokenText.
This is a known issue by the Chromium team that apparently they are not going to fix. There is no Why does
markTextAsSpoken not work on Chrome for Android or Linux?boundary event fired on platforms that only support network-based voices. You can use fetchAudioData to fallback to the HTMLAudioElement, or try a different browser/OS.
See the compat table on MDN for SpeechSynthesis.pause().Why can I not pause the audio when using
SpeechSynthesis on Firefox and Chrome for Android?In Android, pause() ends the current utterance. pause() behaves the same as cancel().
You can use the hook useTts to build custom controls that do not expose a pause, but only stop. If using the TextToSpeech component use the useStopOverPause prop for Android devices.
Why is text from
dangerouslySetInnerHTML not spoken?tts-react does not speak text from dangerouslySetInnerHTML. Instead convert your HTML string into React elements via an html-to-react parser. See this example.
Safari is becoming the new IE, and simply does not follow the spec completely (yet). As one example, Safari 15.6.1 on macOS Monterey 12.5.1, throws a What's up with Safari?
SpeechSynthesisEvent during a SpeechSynthesisUtterance.error, while the spec says errors against utterances "must use the SpeechSynthesisErrorEvent interface".