DevExtreme v25.2 is now available.

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

Your search did not match any results.

Vue 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
<template> <div className="speech-to-text-demo"> <div class="speech-to-text-container"> <span>Use voice recognition (speech to text)</span> <DxSpeechToText id="speech-to-text" :class="{ 'animation-disabled': !animation, 'custom-button': displayMode === 'Custom' }" :start-text="startText" :stop-text="stopText" v-model:type="type" v-model:styling-mode="stylingMode" :hint="hint" :speech-recognition-config="speechRecognitionConfig" v-model:disabled="disabled" @start-click="onStartClick" @stop-click="onStopClick" @result="onResult" /> <DxTextArea id="text-area" v-model:value="textAreaValue" :width="360" :height="120" placeholder="Recognized text will appear here..." :input-attr="{ 'aria-label': 'Recognized Text' }" /> <DxButton text="Clear" :disabled="textAreaValue === ''" @click="onClearButtonClick" /> </div> <div class="options"> <div class="caption">Options</div> <div class="option"> <div>Display Mode</div> <DxSelectBox v-model:value="displayMode" :data-source="displayModes" :input-attr="{ 'aria-label': 'Display Mode' }" @value-changed="onDisplayModeChanged" /> </div> <div class="option"> <div>Styling Mode</div> <DxSelectBox v-model="stylingMode" :data-source="stylingModes" display-expr="displayValue" value-expr="value" :disabled="displayMode === 'Custom'" :input-attr="{ 'aria-label': 'Styling Mode' }" /> </div> <div class="option"> <div>Type</div> <DxSelectBox v-model="type" :data-source="types" display-expr="displayValue" value-expr="value" :disabled="displayMode === 'Custom'" :input-attr="{ 'aria-label': 'Type' }" /> </div> <div class="switch"> <DxSwitch v-model="disabled" /> <span>Disabled</span> </div> <div class="option-separator"/> <div class="option"> <div>Language</div> <DxSelectBox v-model="language" :data-source="languages" :input-attr="{ 'aria-label': 'Language' }" /> </div> <div class="switch"> <DxSwitch v-model="interimResults" /> <span>Interim Results</span> </div> <div class="switch"> <DxSwitch v-model="continuous" /> <span>Continuous Recognition</span> </div> <div class="option-separator"/> <div class="switch"> <DxSwitch v-model="animation" /> <span>Animation</span> </div> </div> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import { DxSpeechToText } from 'devextreme-vue/speech-to-text'; import { DxTextArea } from 'devextreme-vue/text-area'; import { DxButton, type DxButtonTypes } from 'devextreme-vue/button'; import { DxSelectBox } from 'devextreme-vue/select-box'; import { DxSwitch } from 'devextreme-vue/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 startText = ref(''); const stopText = ref(''); const displayMode = ref(displayModes[0]); const stylingMode = ref<DxButtonTypes.ButtonStyle>('contained'); const type = ref<DxButtonTypes.ButtonType>('default'); const hint = ref('Start voice recognition'); const disabled = ref(false); const textAreaValue = ref(''); const language = ref<string>(languages[0]); const interimResults = ref(true); const continuous = ref(false); const animation = ref(true); const speechRecognitionConfig: Record<string, any> = computed(() => ({ interimResults: interimResults.value, continuous: continuous.value, lang: langMap[language.value], })); function onStartClick() { 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'; hint.value = 'Stop voice recognition'; if (displayMode.value !== 'Custom') { return; } type.value = 'danger'; } function onStopClick() { stopHandler(); } function onResult({ event }: Record<string, any>) { const { results } = event; const resultText = Object.values(results) .map((resultItem: any) => resultItem[0].transcript.trim()) .join(' '); textAreaValue.value = resultText; if (!continuous.value && results[0].isFinal === true) { stopHandler(); } } function stopHandler() { state = 'initial'; hint.value = 'Start voice recognition'; if (displayMode.value !== 'Custom') { return; } type.value = 'default'; } function onClearButtonClick() { textAreaValue.value = ''; } function onDisplayModeChanged({ value }: { value?: string }) { if (value === 'Text and Icon') { startText.value = 'Dictate'; stopText.value = 'Stop'; return; } startText.value = ''; stopText.value = ''; if (value === 'Custom') { stylingMode.value = 'contained'; type.value = state === 'initial' ? 'default' : 'danger'; } } </script> <style> .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); } .animation-disabled { animation: none; } #speech-to-text.custom-button { border-radius: 2rem; } </style>
window.exports = window.exports || {}; window.config = { transpiler: 'plugin-babel', meta: { '*.vue': { loader: 'vue-loader', }, '*.ts': { loader: 'demo-ts-loader', }, '*.svg': { loader: 'svg-loader', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'project:': '../../../../', 'npm:': 'https://cdn.jsdelivr.net/npm/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, map: { 'vue': 'npm:vue@3.4.27/dist/vue.esm-browser.js', '@vue/shared': 'npm:@vue/shared@3.4.27/dist/shared.cjs.prod.js', 'vue-loader': 'npm:dx-systemjs-vue-browser@1.1.2/index.js', 'demo-ts-loader': 'project:utils/demo-ts-loader.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'svg-loader': 'project:utils/svg-loader.js', 'mitt': 'npm:mitt/dist/mitt.umd.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-vue': 'npm:devextreme-vue@link:../../packages/devextreme-vue/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', '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-vue': { main: 'index.js', }, 'devextreme-vue/common': { main: 'index.js', }, 'devextreme': { defaultExtension: 'js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/common/core/events/utils': { main: 'index', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', ], babelOptions: { sourceMaps: false, stage0: true, }, }; window.process = { env: { NODE_ENV: 'production', }, }; System.config(window.config); // eslint-disable-next-line const useTgzInCSB = ['openai'];
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: Record<string, string> = { "Auto-detect": '', "English": 'en-US', "Spanish": 'es-ES', "French": 'fr-FR', "German": 'de-DE', };
import { createApp } from 'vue'; import themes from 'devextreme/ui/themes'; import App from './App.vue'; themes.initialized(() => createApp(App).mount('#app'));
<!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" /> <script src="https://cdn.jsdelivr.net/npm/typescript@5.9.3/lib/typescript.js"></script> <script type="module"> import * as vueCompilerSFC from "https://cdn.jsdelivr.net/npm/@vue/compiler-sfc@3.4.27/dist/compiler-sfc.esm-browser.js"; window.vueCompilerSFC = vueCompilerSFC; </script> <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.ts"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"> </div> </div> </body> </html>

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.