DevExtreme v25.2 is now available.

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

Your search did not match any results.

Angular 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
<div class="speech-to-text-demo"> <div class="speech-to-text-container"> <span>Use voice recognition (speech to text)</span> <dx-speech-to-text id="speech-to-text" [class.animation-disabled]="!animation" [class.custom-button]="displayMode === 'Custom'" [startText]="startText" [stopText]="stopText" [stylingMode]="stylingMode" [type]="type" [hint]="hint" [disabled]="disabled" [speechRecognitionConfig]="speechRecognitionConfig" (onStartClick)="onStartClick()" (onResult)="onResult($event)" (onEnd)="onEnd()" ></dx-speech-to-text> <dx-text-area id="text-area" [(value)]="textAreaValue" [width]="360" [height]="120" placeholder="Recognized text will appear here..." [inputAttr]="{ 'aria-label': 'Recognized Text' }" ></dx-text-area> <dx-button text="Clear" [disabled]="textAreaValue === ''" (onClick)="onClearButtonClick($event)" ></dx-button> </div> <div class="options"> <div class="caption">Options</div> <div class="option"> <div>Display Mode</div> <dx-select-box [(value)]="displayMode" [dataSource]="displayModes" [inputAttr]="{ 'aria-label': 'Display Mode' }" (onValueChanged)="onDisplayModeChanged($event)" ></dx-select-box> </div> <div class="option"> <div>Styling Mode</div> <dx-select-box [(value)]="stylingMode" [dataSource]="stylingModes" displayExpr="displayValue" valueExpr="value" [disabled]="displayMode === 'Custom'" [inputAttr]="{ 'aria-label': 'Styling Mode' }" ></dx-select-box> </div> <div class="option"> <div>Type</div> <dx-select-box [(value)]="type" [dataSource]="types" displayExpr="displayValue" valueExpr="value" [disabled]="displayMode === 'Custom'" [inputAttr]="{ 'aria-label': 'Type' }" ></dx-select-box> </div> <div class="switch"> <dx-switch [(value)]="disabled"></dx-switch> <span>Disabled</span> </div> <div class="option-separator"></div> <div class="option"> <div>Language</div> <dx-select-box [(value)]="language" [dataSource]="languages" [inputAttr]="{ 'aria-label': 'Language' }" (onValueChanged)="updateSpeechRecognitionConfig()" ></dx-select-box> </div> <div class="switch"> <dx-switch [(value)]="interimResults" (onValueChanged)="updateSpeechRecognitionConfig()" ></dx-switch> <span>Interim Results</span> </div> <div class="switch"> <dx-switch [(value)]="continuous" (onValueChanged)="updateSpeechRecognitionConfig()" ></dx-switch> <span>Continuous Recognition</span> </div> <div class="option-separator"></div> <div class="switch"> <dx-switch [(value)]="animation"></dx-switch> <span>Animation</span> </div> </div> </div>
import { bootstrapApplication } from '@angular/platform-browser'; import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core'; import { DxSpeechToTextModule } from 'devextreme-angular/ui/speech-to-text'; import { DxTextAreaModule } from 'devextreme-angular/ui/text-area'; import { DxButtonModule } from 'devextreme-angular/ui/button'; import { DxSelectBoxModule } from 'devextreme-angular/ui/select-box'; import { DxSwitchModule } from 'devextreme-angular/ui/switch'; import notify from 'devextreme/ui/notify'; import { AppService } from './app.service'; declare global { interface Window { SpeechRecognition?: any; webkitSpeechRecognition?: any; } } if (!/localhost/.test(document.location.host)) { enableProdMode(); } let modulePrefix = ''; // @ts-ignore if (window && window.config?.packageConfigPaths) { modulePrefix = '/app'; } @Component({ selector: 'demo-app', templateUrl: `app/app.component.html`, styleUrls: [`app/app.component.css`], imports: [ DxSpeechToTextModule, DxTextAreaModule, DxButtonModule, DxSelectBoxModule, DxSwitchModule, ], }) export class AppComponent { state: 'initial' | 'listening'; startText: string; stopText: string; displayMode: string; displayModes: string[]; stylingMode: string; stylingModes: { displayValue: string; value: string }[]; type: string; types: { displayValue: string; value: string }[]; hint: string; disabled: boolean; textAreaValue: string; language: string; languages: string[]; langMap: { [key: string]: string }; interimResults: boolean; continuous: boolean; animation: boolean; speechRecognitionConfig: Record<string, unknown>; constructor(private readonly appService: AppService) { this.state = 'initial'; this.startText = ''; this.stopText = ''; this.displayModes = this.appService.getDisplayModes(); this.displayMode = this.displayModes[0]; this.stylingModes = this.appService.getStylingModes(); this.stylingMode = this.stylingModes[0].value; this.types = this.appService.getTypes(); this.type = this.types[2].value; this.hint = 'Start voice recognition'; this.disabled = false; this.textAreaValue = ''; this.languages = this.appService.getLanguages(); this.language = this.languages[0]; this.langMap = this.appService.getLangMap(); this.interimResults = true; this.continuous = false; this.animation = true; this.updateSpeechRecognitionConfig(); } 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; } this.state = 'listening'; this.hint = 'Stop voice recognition'; if (this.displayMode !== 'Custom') { return; } this.type = 'danger'; } onResult({ event }) { const { results } = event; const resultText = Object.values(results) .map((resultItem) => resultItem[0].transcript.trim()) .join(' '); this.textAreaValue = resultText; } onEnd() { this.state = 'initial'; this.hint = 'Start voice recognition'; if (this.displayMode !== 'Custom') { return; } this.type = 'default'; } onClearButtonClick() { this.textAreaValue = ''; } onDisplayModeChanged({ value }) { if (value === 'Text and Icon') { this.startText = 'Dictate'; this.stopText = 'Stop'; return; } this.startText = ''; this.stopText = ''; if (value === 'Custom') { this.stylingMode = 'contained'; this.type = this.state === 'initial' ? 'default' : 'danger'; } } updateSpeechRecognitionConfig() { this.speechRecognitionConfig = { lang: this.langMap[this.language], interimResults: this.interimResults, continuous: this.continuous, }; } } bootstrapApplication(AppComponent, { providers: [ provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), AppService, ], });
::ng-deep .speech-to-text-demo { display: flex; gap: 20px; height: 640px; } ::ng-deep .speech-to-text-container { display: flex; flex-direction: column; row-gap: 16px; flex-grow: 1; align-items: center; justify-content: center; } ::ng-deep #text-area { margin-top: 16px; } ::ng-deep .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; } ::ng-deep .caption { font-weight: 500; font-size: 18px; } ::ng-deep .option { display: flex; flex-direction: column; row-gap: 4px; } ::ng-deep .switch { display: flex; align-items: center; column-gap: 8px; } ::ng-deep .option-separator { border-bottom: 1px solid var(--dx-color-border); } ::ng-deep #speech-to-text.animation-disabled { animation: none; } ::ng-deep #speech-to-text.custom-button { border-radius: 2rem; }
import { Injectable } from '@angular/core'; import { type DxButtonTypes } from 'devextreme-angular/ui/button'; @Injectable({ providedIn: 'root', }) export class AppService { displayModes: string[] = []; stylingModes: { displayValue: string; value: DxButtonTypes.ButtonStyle }[] = []; types: { displayValue: string; value: DxButtonTypes.ButtonType }[] = []; languages: string[] = []; langMap: { [key: string]: string } = {}; constructor() { this.displayModes = ['Icon Only', 'Text and Icon', 'Custom']; this.stylingModes = [ { displayValue: 'Contained', value: 'contained', }, { displayValue: 'Outlined', value: 'outlined', }, { displayValue: 'Text', value: 'text', }, ]; this.types = [ { displayValue: 'Normal', value: 'normal', }, { displayValue: 'Success', value: 'success', }, { displayValue: 'Default', value: 'default', }, { displayValue: 'Danger', value: 'danger', }, ]; this.languages = [ 'Auto-detect', 'English', 'Spanish', 'French', 'German', ]; this.langMap = { 'Auto-detect': '', 'English': 'en-US', 'Spanish': 'es-ES', 'French': 'fr-FR', 'German': 'de-DE', }; } getDisplayModes() { return this.displayModes; } getStylingModes() { return this.stylingModes; } getTypes() { return this.types; } getLanguages() { return this.languages; } getLangMap() { return this.langMap; } }
// In real applications, you should not transpile code in the browser. // You can see how to create your own application with Angular and DevExtreme here: // https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/ const componentNames = [ 'accordion', 'action-sheet', 'autocomplete', 'bar-gauge', 'box', 'bullet', 'button-group', 'button', 'calendar', 'card-view', 'chart', 'chat', 'check-box', 'circular-gauge', 'color-box', 'context-menu', 'data-grid', 'date-box', 'date-range-box', 'defer-rendering', 'diagram', 'draggable', 'drawer', 'drop-down-box', 'drop-down-button', 'file-manager', 'file-uploader', 'filter-builder', 'form', 'funnel', 'gallery', 'gantt', 'html-editor', 'linear-gauge', 'list', 'load-indicator', 'load-panel', 'lookup', 'map', 'menu', 'multi-view', 'nested', 'number-box', 'pagination', 'pie-chart', 'pivot-grid-field-chooser', 'pivot-grid', 'polar-chart', 'popover', 'popup', 'progress-bar', 'radio-group', 'range-selector', 'range-slider', 'recurrence-editor', 'resizable', 'responsive-box', 'sankey', 'scheduler', 'scroll-view', 'select-box', 'slider', 'sortable', 'sparkline', 'speech-to-text', 'speed-dial-action', 'splitter', 'stepper', 'switch', 'tab-panel', 'tabs', 'tag-box', 'text-area', 'text-box', 'tile-view', 'toast', 'toolbar', 'tooltip', 'tree-list', 'tree-map', 'tree-view', 'validation-group', 'validation-summary', 'validator', 'vector-map', ]; window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, }, meta: { 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, '@angular/platform-browser-dynamic': { 'esModule': true, }, '@angular/platform-browser': { 'esModule': true, }, '@angular/core': { 'esModule': true, }, '@angular/common': { 'esModule': true, }, '@angular/common/http': { 'esModule': true, }, '@angular/animations': { 'esModule': true, }, '@angular/forms': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://cdn.jsdelivr.net/npm/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, 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', /* @angular */ '@angular/compiler': 'bundles:@angular/compiler.umd.js', '@angular/platform-browser-dynamic': 'bundles:@angular/platform-browser-dynamic.umd.js', '@angular/core': 'bundles:@angular/core.umd.js', '@angular/core/primitives/signals': 'bundles:@angular/core.primitives.signals.umd.js', '@angular/core/primitives/di': 'bundles:@angular/core.primitives.di.umd.js', '@angular/common': 'bundles:@angular/common.umd.js', '@angular/common/http': 'bundles:@angular/common-http.umd.js', '@angular/platform-browser': 'bundles:@angular/platform-browser.umd.js', '@angular/platform-browser/animations': 'bundles:@angular/platform-browser.umd.js', '@angular/forms': 'bundles:@angular/forms.umd.js', /* devextreme */ 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', 'devextreme-quill': 'npm:devextreme-quill@1.7.7/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.24', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.64', /* devextreme-angular umd maps */ 'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js', 'devextreme-angular/common/ai-integration': 'bundles:devextreme-angular/devextreme-angular-common-ai-integration.umd.js', 'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js', 'devextreme-angular/common/charts': 'bundles:devextreme-angular/devextreme-angular-common-charts.umd.js', 'devextreme-angular/common/core/animation': 'bundles:devextreme-angular/devextreme-angular-common-core-animation.umd.js', 'devextreme-angular/common/core/environment': 'bundles:devextreme-angular/devextreme-angular-common-core-environment.umd.js', 'devextreme-angular/common/core/events': 'bundles:devextreme-angular/devextreme-angular-common-core-events.umd.js', 'devextreme-angular/common/core/localization': 'bundles:devextreme-angular/devextreme-angular-common-core-localization.umd.js', 'devextreme-angular/common/core': 'bundles:devextreme-angular/devextreme-angular-common-core.umd.js', 'devextreme-angular/common/data/custom-store': 'bundles:devextreme-angular/devextreme-angular-common-data-custom-store.umd.js', 'devextreme-angular/common/data': 'bundles:devextreme-angular/devextreme-angular-common-data.umd.js', 'devextreme-angular/common/export/excel': 'bundles:devextreme-angular/devextreme-angular-common-export-excel.umd.js', 'devextreme-angular/common/export/pdf': 'bundles:devextreme-angular/devextreme-angular-common-export-pdf.umd.js', 'devextreme-angular/common/export': 'bundles:devextreme-angular/devextreme-angular-common-export.umd.js', 'devextreme-angular/common/grids': 'bundles:devextreme-angular/devextreme-angular-common-grids.umd.js', 'devextreme-angular/common': 'bundles:devextreme-angular/devextreme-angular-common.umd.js', 'devextreme-angular/http': 'bundles:devextreme-angular/devextreme-angular-http.umd.js', 'devextreme-angular/core/tokens': 'bundles:devextreme-angular/devextreme-angular-core-tokens.umd.js', ...componentNames.reduce((acc, name) => { acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`; acc[`devextreme-angular/ui/${name}/nested`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}-nested.umd.js`; return acc; }, {}), 'tslib': 'npm:tslib/tslib.js', 'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js', 'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.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', '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', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', 'zone.js': 'npm:zone.js@0.15.1/bundles/zone.umd.js', }, packages: { 'app': { main: './app.component.ts', defaultExtension: 'ts', }, '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', }, 'rxjs': { defaultExtension: 'js', }, 'rxjs/operators': { defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:rxjs@7.5.3/package.json', 'npm:rxjs@7.5.3/operators/package.json', 'npm:devexpress-diagram@2.2.24/package.json', 'npm:devexpress-gantt@4.1.64/package.json', ], }; window.process = { env: { NODE_ENV: 'production', }, }; System.config(window.config); // eslint-disable-next-line no-console // System.import('@angular/compiler').catch(console.error.bind(console)); // eslint-disable-next-line const useTgzInCSB = ['openai']; let packagesInfo = { "@angular/core": { "version": "21.0.5" }, "core-js": { "version": "2.6.12" }, "typescript": { "version": "5.9.3" }, "zone.js": { "version": "0.15.1" } };
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" 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/core-js@2.6.12/client/shim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/zone.js@0.15.1/bundles/zone.umd.js"></script> <script src="https://cdn.jsdelivr.net/npm/reflect-metadata@0.1.13/Reflect.js"></script> <script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script> <script src="config.js"></script> <script> System.import("app").catch(console.error.bind(console)); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </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.