Speech Add-ins
Speech add-ins are optional packages that give existing controls a voice. They are ordinary tools and
actions — you attach them to a ChatView or a TextEntry the same way you attach any other one — so
nothing about the host control changes, and dropping the package removes the capability cleanly.
They ship separately because speech pulls in Shiny.Speech and its platform permissions. Apps that never
dictate anything shouldn’t carry that weight.
What’s in the box
Section titled “What’s in the box”| Tool | Host | What it does |
|---|---|---|
SpeechToTextTool |
ChatView input bar (MAUI) | Listens via ISpeechToTextService and backfills the entry, optionally submitting when recognition completes |
TextToSpeechBubbleTool |
ChatView message bubble (MAUI) | Reads a message’s Body aloud via ITextToSpeechService |
TextEntrySpeechToTextTool |
Any TextEntry (MAUI) |
Appends recognized speech to the field, with a listening indicator |
TextEntrySpeechToTextButton |
Any input (Blazor) | The same voice-input button, driven by the browser’s Web Speech API |
PromptTextToSpeechTool |
Quick Entry PromptView (MAUI and Blazor) |
Reads the prompt’s answer aloud via ITextToSpeechService |
.NET MAUI
Section titled “.NET MAUI”Install Shiny.Maui.Controls.SpeechAddins and register Shiny Speech — AddSpeechServices() (or just
AddTextToSpeech() for read-aloud on its own). Every tool here resolves its engine from DI and no-ops
when nothing is registered, so a forgotten registration looks like a button that does nothing. The tools are registered under the
same http://shiny.net/maui/controls XAML namespace as the core controls, so there is no extra xmlns
to add.
Speech recognition needs a microphone permission entry in the app manifest — a library cannot add it for you. See Shiny.Speech for the per-platform requirements.
Blazor
Section titled “Blazor”Install Shiny.Blazor.Controls.SpeechAddins and add the @using — typically in _Imports.razor:
@using Shiny.Blazor.Controls.SpeechAddinsRecognition uses the browser’s Web Speech API, which is not implemented everywhere. Chromium-based browsers support it; Firefox does not. The button is a no-op where the API is missing rather than an error.
ChatView
Section titled “ChatView”Add the tools like any other action:
<shiny:ChatView Provider="{Binding Provider}" SessionId="{Binding SessionId}"> <shiny:ChatView.InputActions> <shiny:SpeechToTextTool AutoSend="False" SilenceTimeout="00:00:03" /> </shiny:ChatView.InputActions> <shiny:ChatView.CustomBubbleActions> <shiny:TextToSpeechBubbleTool /> </shiny:ChatView.CustomBubbleActions></shiny:ChatView>Or in code:
using Shiny.Maui.Controls.SpeechAddins.Chat;
this.InputActions = [ new SpeechToTextTool { AutoSend = false, SilenceTimeout = TimeSpan.FromSeconds(3) } ];this.CustomBubbleActions = [ new TextToSpeechBubbleTool() ];SpeechToTextTool |
Default | |
|---|---|---|
AutoSend |
false |
submit the entry after recognition completes |
SilenceTimeout |
2s |
silence before recognition is considered done |
Culture |
null |
recognition culture (e.g. "en-US") |
PreferOnDevice |
false |
prefer on-device recognition |
ListeningText |
⏹ |
glyph shown while listening |
TextToSpeechBubbleTool |
Default |
|---|---|
SpeechRate / Pitch / Volume |
1.0 |
Culture / VoiceName |
null (system default) |
TextEntry
Section titled “TextEntry”TextEntrySpeechToTextTool is a TextEntryTool, so it sits in the entry’s tool strip beside things like
ClearButtonTool:
<shiny:TextEntry Placeholder="Say something"> <shiny:TextEntry.RightTools> <shiny:TextEntrySpeechToTextTool Culture="en-US" SilenceTimeout="00:00:02" /> </shiny:TextEntry.RightTools></shiny:TextEntry>TextEntrySpeechToTextTool |
Default | |
|---|---|---|
SilenceTimeout |
2s |
silence before recognition is considered done |
Culture |
null |
recognition culture |
PreferOnDevice |
false |
prefer on-device recognition |
ListeningText |
⏹ |
glyph shown while listening |
ListeningColor |
— | tint applied while listening |
The tool restores the entry’s original text if recognition is cancelled, so an aborted dictation doesn’t leave a half-transcribed field behind.
Blazor
Section titled “Blazor”<TextEntrySpeechToTextButton @bind-Text="message" Culture="en-US" />| Parameter | Default |
|---|---|
Text / TextChanged |
— (bindable) |
IdleText / ListeningText |
🎙 / ⏹ |
IdleColor / ListeningColor |
#4CAF50 / #F44336 |
Culture |
null |
Continuous |
false |
Quick Entry
Section titled “Quick Entry”PromptTextToSpeechTool is a PromptTool, so it docks into a PromptView’s tool slots — the same
shape the TextEntry tools above have. It ships on both hosts.
<qe:PromptView> <qe:PromptView.TrailingTools> <shiny:PromptTextToSpeechTool AutoSpeak="True" Culture="en-US" /> </qe:PromptView.TrailingTools></qe:PromptView><PromptView Response="@answer" TrailingTools="tools" />
@code { readonly List<PromptTool> tools = new() { new PromptTextToSpeechTool() };}PromptTextToSpeechTool |
Default | |
|---|---|---|
AutoSpeak |
false |
read the answer as soon as it lands |
HideWhenEmpty |
true |
hide the tool until there is something to read |
TextSelector |
null |
Func<PromptView, string?> — what to read |
SpeechRate / Pitch / Volume |
1.0 |
passed through to TextToSpeechOptions |
Culture / VoiceName |
null (system default) |
|
SpeakingText / SpeakingColor |
⏹ / #F44336 |
the stop-state glyph and tint (Blazor: SpeakingIcon) |
It speaks PromptView.Response — the plain-text half of the answer. A rich answer set through
ResponseContent has no text in it to hand a synthesiser, so give the tool a TextSelector that pulls
the words out of what you rendered.
Next Steps
Section titled “Next Steps”- Quick Entry — the prompt these dock into
- ChatView Custom Actions — how input actions and bubble actions fit together
- TextEntry — the tool strip these plug into
- Shiny.Speech — the underlying recognition and synthesis services


