DevExtreme v25.2 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Speech To Text

The DevExtreme SpeechToText component allows you to integrate voice input into your DevExtreme-powered app. The component implements the Web Speech API SpeechRecognition interface and supports custom speech recognizers.

Backend API
import React, { useCallback, useState } from 'react'; import { SpeechToText } from 'devextreme-react/speech-to-text'; import { TextArea } from 'devextreme-react/text-area'; import { Button, ButtonTypes } from 'devextreme-react/button'; import { SelectBox } from 'devextreme-react/select-box'; import { Switch } from 'devextreme-react/switch'; import notify from 'devextreme/ui/notify'; import { displayModes, stylingModes, types, languages, langMap } from './data.ts'; declare global { interface Window { SpeechRecognition: any; webkitSpeechRecognition: any; } } let state = 'initial'; const textAreaLabel = { 'aria-label': 'Recognized Text' }; const displayModeLabel = { 'aria-label': 'Display Mode' }; const stylingModeLabel = { 'aria-label': 'Styling Mode' }; const typeLabel = { 'aria-label': 'Type' }; const languageLabel = { 'aria-label': 'Language' }; export default function App() { const [startText, setStartText] = useState<string>(''); const [stopText, setStopText] = useState<string>(''); const [displayMode, setDisplayMode] = useState<string>(displayModes[0]); const [stylingMode, setStylingMode] = useState<ButtonTypes.ButtonStyle>(stylingModes[0].value); const [type, setType] = useState<ButtonTypes.ButtonType>(types[2].value); const [hint, setHint] = useState<string>('Start voice recognition'); const [disabled, setDisabled] = useState<boolean>(false); const [textAreaValue, setTextAreaValue] = useState<string>(''); const [language, setLanguage] = useState<string>(languages[0]); const [interimResults, setInterimResults] = useState<boolean>(true); const [continuous, setContinuous] = useState<boolean>(false); const [animation, setAnimation] = useState<boolean>(true); const speechRecognitionConfig = { lang: langMap[language], interimResults, continuous, }; const onStartClick = useCallback(() => { if (!window.SpeechRecognition && !window.webkitSpeechRecognition) { notify({ message: 'The browser does not support Web Speech API (SpeechRecognition).', type: 'error', displayTime: 7000, position: 'bottom center', width: 'auto', }); return; } state = 'listening'; setHint('Stop voice recognition'); if (displayMode !== 'Custom') { return; } setType('danger'); }, [displayMode]); const onEnd = useCallback(() => { state = 'initial'; setHint('Start voice recognition'); if (displayMode !== 'Custom') { return; } setType('default'); }, [displayMode]); const onResult = useCallback(({ event }) => { const { results } = event; const resultText = Object.values(results) .map((resultItem: any) => resultItem[0].transcript.trim()) .join(' '); setTextAreaValue(resultText); }, []); const onTextAreaValueChanged = useCallback(({ value }) => { setTextAreaValue(value); }, []); const onClearButtonClick = useCallback(() => { setTextAreaValue(''); }, []); const onDisplayModeValueChanged = useCallback(({ value }) => { setDisplayMode(value); if (value === 'Text and Icon') { setStartText('Dictate'); setStopText('Stop'); return; } setStartText(''); setStopText(''); if (value === 'Custom') { setStylingMode('contained'); setType(state === 'initial' ? 'default' : 'danger'); } }, []); const onStylingModeValueChanged = useCallback(({ value }) => { setStylingMode(value); }, []); const onTypeValueChanged = useCallback(({ value }) => { setType(value); }, []); const onDisabledValueChanged = useCallback(({ value }) => { setDisabled(value); }, []); const onLanguageValueChanged = useCallback(({ value }) => { setLanguage(value); }, []); const onInterimResultsValueChanged = useCallback(({ value }) => { setInterimResults(value); }, []); const onContinuousValueChanged = useCallback(({ value }) => { setContinuous(value); }, []); const onAnimationValueChanged = useCallback(({ value }) => { setAnimation(value); }, []); return ( <div className='speech-to-text-demo'> <div className='speech-to-text-container'> <span>Use voice recognition (speech to text)</span> <SpeechToText id='speech-to-text' className={ `${!animation ? 'animation-disabled' : ''} ${displayMode === 'Custom' ? 'custom-button' : ''}` } startText={startText} stopText={stopText} stylingMode={stylingMode} type={type} hint={hint} speechRecognitionConfig={speechRecognitionConfig} disabled={disabled} onStartClick={onStartClick} onStopClick={onEnd} onEnd={onEnd} onResult={onResult} /> <TextArea id='text-area' value={textAreaValue} width={360} height={120} placeholder='Recognized text will appear here...' inputAttr={textAreaLabel} onValueChanged={onTextAreaValueChanged} /> <Button text='Clear' disabled={textAreaValue === ''} onClick={onClearButtonClick} /> </div> <div className='options'> <div className='caption'>Options</div> <div className='option'> <div>Display Mode</div> <SelectBox value={displayMode} dataSource={displayModes} inputAttr={displayModeLabel} onValueChanged={onDisplayModeValueChanged} /> </div> <div className='option'> <div>Styling Mode</div> <SelectBox value={stylingMode} dataSource={stylingModes} valueExpr='value' displayExpr='displayValue' disabled={displayMode === 'Custom'} inputAttr={stylingModeLabel} onValueChanged={onStylingModeValueChanged} /> </div> <div className='option'> <div>Type</div> <SelectBox value={type} dataSource={types} valueExpr='value' displayExpr='displayValue' disabled={displayMode === 'Custom'} inputAttr={typeLabel} onValueChanged={onTypeValueChanged} /> </div> <div className='switch'> <Switch value={disabled} onValueChanged={onDisabledValueChanged} /> <span>Disabled</span> </div> <div className='option-separator'></div> <div className='option'> <div>Language</div> <SelectBox value={language} dataSource={languages} inputAttr={languageLabel} onValueChanged={onLanguageValueChanged} /> </div> <div className='switch'> <Switch value={interimResults} onValueChanged={onInterimResultsValueChanged} /> <span>Interim Results</span> </div> <div className='switch'> <Switch value={continuous} onValueChanged={onContinuousValueChanged} /> <span>Continuous Recognition</span> </div> <div className='option-separator'></div> <div className='switch'> <Switch value={animation} onValueChanged={onAnimationValueChanged} /> <span>Animation</span> </div> </div> </div> ); }
import React, { useCallback, useState } from 'react'; import { SpeechToText } from 'devextreme-react/speech-to-text'; import { TextArea } from 'devextreme-react/text-area'; import { Button } from 'devextreme-react/button'; import { SelectBox } from 'devextreme-react/select-box'; import { Switch } from 'devextreme-react/switch'; import notify from 'devextreme/ui/notify'; import { displayModes, stylingModes, types, languages, langMap, } from './data.js'; let state = 'initial'; const textAreaLabel = { 'aria-label': 'Recognized Text' }; const displayModeLabel = { 'aria-label': 'Display Mode' }; const stylingModeLabel = { 'aria-label': 'Styling Mode' }; const typeLabel = { 'aria-label': 'Type' }; const languageLabel = { 'aria-label': 'Language' }; export default function App() { const [startText, setStartText] = useState(''); const [stopText, setStopText] = useState(''); const [displayMode, setDisplayMode] = useState(displayModes[0]); const [stylingMode, setStylingMode] = useState(stylingModes[0].value); const [type, setType] = useState(types[2].value); const [hint, setHint] = useState('Start voice recognition'); const [disabled, setDisabled] = useState(false); const [textAreaValue, setTextAreaValue] = useState(''); const [language, setLanguage] = useState(languages[0]); const [interimResults, setInterimResults] = useState(true); const [continuous, setContinuous] = useState(false); const [animation, setAnimation] = useState(true); const speechRecognitionConfig = { lang: langMap[language], interimResults, continuous, }; const onStartClick = useCallback(() => { if (!window.SpeechRecognition && !window.webkitSpeechRecognition) { notify({ message: 'The browser does not support Web Speech API (SpeechRecognition).', type: 'error', displayTime: 7000, position: 'bottom center', width: 'auto', }); return; } state = 'listening'; setHint('Stop voice recognition'); if (displayMode !== 'Custom') { return; } setType('danger'); }, [displayMode]); const onEnd = useCallback(() => { state = 'initial'; setHint('Start voice recognition'); if (displayMode !== 'Custom') { return; } setType('default'); }, [displayMode]); const onResult = useCallback(({ event }) => { const { results } = event; const resultText = Object.values(results) .map((resultItem) => resultItem[0].transcript.trim()) .join(' '); setTextAreaValue(resultText); }, []); const onTextAreaValueChanged = useCallback(({ value }) => { setTextAreaValue(value); }, []); const onClearButtonClick = useCallback(() => { setTextAreaValue(''); }, []); const onDisplayModeValueChanged = useCallback(({ value }) => { setDisplayMode(value); if (value === 'Text and Icon') { setStartText('Dictate'); setStopText('Stop'); return; } setStartText(''); setStopText(''); if (value === 'Custom') { setStylingMode('contained'); setType(state === 'initial' ? 'default' : 'danger'); } }, []); const onStylingModeValueChanged = useCallback(({ value }) => { setStylingMode(value); }, []); const onTypeValueChanged = useCallback(({ value }) => { setType(value); }, []); const onDisabledValueChanged = useCallback(({ value }) => { setDisabled(value); }, []); const onLanguageValueChanged = useCallback(({ value }) => { setLanguage(value); }, []); const onInterimResultsValueChanged = useCallback(({ value }) => { setInterimResults(value); }, []); const onContinuousValueChanged = useCallback(({ value }) => { setContinuous(value); }, []); const onAnimationValueChanged = useCallback(({ value }) => { setAnimation(value); }, []); return ( <div className="speech-to-text-demo"> <div className="speech-to-text-container"> <span>Use voice recognition (speech to text)</span> <SpeechToText id="speech-to-text" className={`${!animation ? 'animation-disabled' : ''} ${ displayMode === 'Custom' ? 'custom-button' : '' }`} startText={startText} stopText={stopText} stylingMode={stylingMode} type={type} hint={hint} speechRecognitionConfig={speechRecognitionConfig} disabled={disabled} onStartClick={onStartClick} onStopClick={onEnd} onEnd={onEnd} onResult={onResult} /> <TextArea id="text-area" value={textAreaValue} width={360} height={120} placeholder="Recognized text will appear here..." inputAttr={textAreaLabel} onValueChanged={onTextAreaValueChanged} /> <Button text="Clear" disabled={textAreaValue === ''} onClick={onClearButtonClick} /> </div> <div className="options"> <div className="caption">Options</div> <div className="option"> <div>Display Mode</div> <SelectBox value={displayMode} dataSource={displayModes} inputAttr={displayModeLabel} onValueChanged={onDisplayModeValueChanged} /> </div> <div className="option"> <div>Styling Mode</div> <SelectBox value={stylingMode} dataSource={stylingModes} valueExpr="value" displayExpr="displayValue" disabled={displayMode === 'Custom'} inputAttr={stylingModeLabel} onValueChanged={onStylingModeValueChanged} /> </div> <div className="option"> <div>Type</div> <SelectBox value={type} dataSource={types} valueExpr="value" displayExpr="displayValue" disabled={displayMode === 'Custom'} inputAttr={typeLabel} onValueChanged={onTypeValueChanged} /> </div> <div className="switch"> <Switch value={disabled} onValueChanged={onDisabledValueChanged} /> <span>Disabled</span> </div> <div className="option-separator"></div> <div className="option"> <div>Language</div> <SelectBox value={language} dataSource={languages} inputAttr={languageLabel} onValueChanged={onLanguageValueChanged} /> </div> <div className="switch"> <Switch value={interimResults} onValueChanged={onInterimResultsValueChanged} /> <span>Interim Results</span> </div> <div className="switch"> <Switch value={continuous} onValueChanged={onContinuousValueChanged} /> <span>Continuous Recognition</span> </div> <div className="option-separator"></div> <div className="switch"> <Switch value={animation} onValueChanged={onAnimationValueChanged} /> <span>Animation</span> </div> </div> </div> ); }
import React from 'react'; import ReactDOM from 'react-dom'; import themes from 'devextreme/ui/themes'; import App from './App.tsx'; themes.initialized(() => { ReactDOM.render( <App />, document.getElementById('app'), ); });
import { ButtonTypes } from 'devextreme-react/button'; export const displayModes = ['Icon Only', 'Text and Icon', 'Custom']; export const stylingModes: { displayValue: string; value: ButtonTypes.ButtonStyle }[] = [ { displayValue: 'Contained', value: 'contained', }, { displayValue: 'Outlined', value: 'outlined', }, { displayValue: 'Text', value: 'text', }, ]; export const types: { displayValue: string; value: ButtonTypes.ButtonType }[] = [ { displayValue: 'Normal', value: 'normal', }, { displayValue: 'Success', value: 'success', }, { displayValue: 'Default', value: 'default', }, { displayValue: 'Danger', value: 'danger', }, ]; export const languages = [ 'Auto-detect', 'English', 'Spanish', 'French', 'German', ]; export const langMap = { "Auto-detect": '', "English": 'en-US', "Spanish": 'es-ES', "French": 'fr-FR', "German": 'de-DE', };
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://cdn.jsdelivr.net/npm/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', 'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs', 'devextreme-quill': 'npm:devextreme-quill@1.7.7/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.24/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.64/dist/dx-gantt.js', 'inferno': 'npm:inferno@8.2.3/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@8.2.3/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', '@preact/signals-core': 'npm:@preact/signals-core@1.8.0/dist/signals-core.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme-react/common': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/common/core/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; window.process = { env: { NODE_ENV: 'production', }, }; System.config(window.config); // eslint-disable-next-line const useTgzInCSB = ['openai'];
import React from 'react'; import ReactDOM from 'react-dom'; import themes from 'devextreme/ui/themes'; import App from './App.js'; themes.initialized(() => { ReactDOM.render(<App />, document.getElementById('app')); });
export const displayModes = ['Icon Only', 'Text and Icon', 'Custom']; export const stylingModes = [ { displayValue: 'Contained', value: 'contained', }, { displayValue: 'Outlined', value: 'outlined', }, { displayValue: 'Text', value: 'text', }, ]; export const types = [ { displayValue: 'Normal', value: 'normal', }, { displayValue: 'Success', value: 'success', }, { displayValue: 'Default', value: 'default', }, { displayValue: 'Danger', value: 'danger', }, ]; export const languages = ['Auto-detect', 'English', 'Spanish', 'French', 'German']; export const langMap = { 'Auto-detect': '', English: 'en-US', Spanish: 'es-ES', French: 'fr-FR', German: 'de-DE', };
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.2.3/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.speech-to-text-demo { display: flex; gap: 20px; height: 640px; } .speech-to-text-container { display: flex; flex-direction: column; row-gap: 16px; flex-grow: 1; align-items: center; justify-content: center; } #text-area { margin-top: 16px; } .options { display: flex; flex-direction: column; flex-shrink: 0; width: 300px; box-sizing: border-box; padding: 20px; background-color: rgba(191, 191, 191, 0.15); gap: 16px; } .caption { font-weight: 500; font-size: 18px; } .option { display: flex; flex-direction: column; row-gap: 4px; } .switch { display: flex; align-items: center; column-gap: 8px; } .option-separator { border-bottom: 1px solid var(--dx-color-border); } #speech-to-text.animation-disabled { animation: none; } #speech-to-text.custom-button { border-radius: 2rem; }

You can integrate SpeechToText with any text input, including other DevExtreme components. To introduce this capability, set a component's value property to transcribed text. SpeechToText returns transcribed text in the onResult handler as users speak. When speech stops, the component calls the onEnd handler and switches from a "listening" state to the initial state. SpeechToText implements different icon (startIcon/stopIcon), text (startText/stopText), and click handler (onStartClick/onStopClick) properties in each component state.

For a complete overview of SpeechToText options (including Web Speech API options), refer to the following topic: SpeechToText API Reference.