Headless / Transcription
Dictation
Dictate into an existing field with microphone, revision and recovery controls.
37 public parts · 2examples
@slices.sh/transcription/headlessView as Markdown ↗Your design. Our behavior.
Explore the tree to see what each part controls. Gates reveal the relevant state; roots outline their scope.
Dictation.Root · Scope
Installation
Choose what your component needs. Run these commands in an initialized Next.js app.
Before you start
The steps below run in a Next.js app with shadcn set up (it has a components.json). In an existing Next.js app, such as one from create-next-app, set shadcn up first:
npx shadcn@latest init -t next -b base -p novaOr start fresh: the slices CLI creates the app and sets it up.
npx slices.sh installInstall the service registry
Adds services/index.ts, which import "@/services" loads. A provider setup writes the same file, so skip this if your app has one.
npx add servicesInstall the headless library
npmAdd the React parts to your app, then compose them with your own elements.
npm install @slices.sh/transcription@^0.1.11Import from @slices.sh/transcription/headless.
Install the service
Connect the parts to a provider. Skip this step if this service is already configured in your app.
npx add transcription.browserAdds service configuration and API routes to your app, with the provider dependencies. For a headless composition, add import "@/services" after "use client".
The slices CLI installs the service and provisions its required resources in one command.
Provider setup notes
- There is no route to install: this provider has no server half. The shared route app/api/transcription/[config]/[[...path]]/route.ts comes with the first provider that has one (deepgram, gladia, openai).
- Firefox has no speech recogniser: the config then grants nothing and the microphone parts do not render.
Optional: Voice visualizers
Before you start
The steps below run in a Next.js app with shadcn set up (it has a components.json). In an existing Next.js app, such as one from create-next-app, set shadcn up first:
npx shadcn@latest init -t next -b base -p novaOr start fresh: the slices CLI creates the app and sets it up.
npx slices.sh installInstall the headless library
npmAdd the React parts to your app, then compose them with your own elements.
npm install @slices.sh/voice-canvasImport from @slices.sh/voice-canvas.
These parts run locally. No service installation or provisioning is needed.
Start small
Parts bring the behavior. Use their default elements, or supply your own with render.
"use client"// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)import "@/services"import { Dictation } from "@slices.sh/transcription/headless"export function Example() { return ( <Dictation.Root> <Dictation.Field render={<textarea aria-label="Notes" />} /> <Dictation.MicButton>Dictate</Dictation.MicButton> <Dictation.Listening>Listening…</Dictation.Listening> <Dictation.MicDenied> Allow microphone access to continue. </Dictation.MicDenied> </Dictation.Root> )}The browser provider requires a supported browser and microphone permission. Remote providers have different capabilities.
API reference
Every public part used by this experience is listed below. Gates render children without adding an element. Element parts accept native element props and render; function children receive the state declared in their props. Scopes provide context rather than a visual element.
Dictation.Rootscope
Root holds the dictation state for the field below it. With the service it opens the microphone, picks the mode from the config's capabilities (live when it can, record then transcribe when it has file only) and keeps one recording for a retry. With enabled="optional" and no service, it renders its children and nothing else.
Props
9 documented props
| Prop | Type | Default |
|---|---|---|
enabledLeft out or | Enabled | — |
configThe transcription config, | string | — |
valueThe field's text, where an app already holds it — in a form, say. Left out, this root holds it, which is what lets a pattern be nothing but markup. → docs/rules/patterns-read-no-state.md | string | — |
languages | string[] | — |
maxDurationMsA ceiling: a microphone left open bills without producing anything. | number | — |
stopAfterSilenceMsHow long a quiet has to last before it finishes on its own. | number | 0 |
shortcuts⌘⇧Space starts and finishes, Escape discards. On the document, so somebody already typing need not reach for the microphone. Off by default, because a chord bound by a component nobody asked to bind it is a chord that fights whatever the page around it already does with that key. → hooks/transcription/use-dictation-keys.ts | boolean | false |
revisionsOffer the definitive text when it disagrees with an edit. Off, for a screen with no
| boolean | true |
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/dictation-root.tsx
Dictation.Fieldinput · <textarea>
The field being dictated into, which somebody may also type in. It wraps any text input: give it the input as `render` (a `Composer.Input`, an address input, a plain textarea) and it takes over four props of it: `value` and `onChange`, through which recognised words land at the caret, `aria-busy`, true while listening or finalizing, which is the whole of its link to a form, and `onKeyDown`, where Escape cancels. It knows nothing about forms or messages. Without a transcription service it renders its input untouched. It is a part rather than the pattern's own textarea so the text lives in the root: what a person types and what dictation writes are the same value, and two owners of it would disagree the first time both happened at once. It also owns the caret, in both directions. Where it is goes up, because dictation starts there. Where it belongs comes back down as arithmetic rather than as a number from anybody: a controlled textarea whose value changed puts the caret at the end, and every committed segment changes the value — so on a change this element did not make, the caret goes to the end of what changed, which is exactly the end of the dictated run.
Required scope
Dictation.Field must be inside <Dictation.Root>.
Props
2 documented props · Native <textarea> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/field.tsx
Dictation.Markscontent · <div>
The field's text again, with the span dictation just wrote singled out. A textarea cannot colour a range of its own value. The only way to point at the words that have just arrived is a second element behind it with the same metrics, holding the same text invisibly and marking the run — so the eye finds the new text and the field was never anything but a textarea, with its own caret, its own selection and its own spellcheck. The three pieces are handed over rather than drawn, because which of them is marked is this layer's and what a mark looks like is not. → docs/rules/words-in-copy-looks-in-skin.md
Required scope
Dictation.Marks must be inside <Dictation.Root>.
Props
2 documented props · Native <div> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { before: string; run: string; after: string; at: Marked }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/marks.tsx
Dictation.MicButtonaction · <button>
The one button: starts when idle, finishes when listening. One control rather than two, because that is what everybody expects from a microphone, and because a separate stop button spends most of its life disabled beside it.
Required scope
Dictation.MicButton must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { listening: boolean }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/mic-button.tsx
Dictation.MicLevelcontent · <meter>
How loud it is, as a real `<meter>`. Read from the local analyser rather than from anything the provider sends, so it follows the voice rather than the network. Rendered only while listening: a meter at zero next to an idle button says nothing. `Dictation.Level` is the same number without the element, for a skin that draws its own.
Required scope
Dictation.MicLevel must be inside <Dictation.Root>.
Props
2 documented props · Native <meter> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { level: number }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/mic-level.tsx
Dictation.Levelcontent · <div>
How loud it is, as a number somebody else draws. `Dictation.MicLevel` is a real `<meter>`, which is the right answer when a meter is what is wanted. This is for the other one: twelve bars, a ring, a blob that breathes — a skin that is given 0–1 and decides. Neither belongs in the other, because an element the browser styles and an element a designer draws are different things. Rendered only while listening: a level of zero beside an idle button says nothing.
Required scope
Dictation.Level must be inside <Dictation.Root>.
Props
2 documented props · Native <div> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { level: number; speaking: boolean }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/level.tsx
Dictation.Signalcontent · <div>
The voice itself, handed to whatever draws it. The one part here that gives out an object with functions on it rather than a number, and deliberately: a waveform wants a fresh reading every frame, and a part that put those numbers into React state would re-render the field somebody is dictating into sixty times a second. What goes across is the analyser; the drawing loop belongs to the skin, and it reads from its own `requestAnimationFrame` without this layer hearing about any of it. → services/transcription/signal.ts `mode` is what the drawing is of rather than what it looks like: a voice arriving, a pass being waited on, or a microphone that is not open. Which of those is a fact about the dictation, and the only one this layer is entitled to state.
Required scope
Dictation.Signal must be inside <Dictation.Root>.
Props
2 documented props · Native <div> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { source: SignalSource | null; mode: "idle" | "listening" | "processing" }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/signal.tsx
Dictation.Waveformcontent · <div>
The last couple of seconds of voice, oldest first, for a skin to draw as it likes. A history rather than a spectrum. A bar per frequency band is a picture of the sound; a bar per moment is a picture of the *speaking*, and it is the second one people read — which is also why it scrolls, since a shape that only rises and falls in place cannot show a pause. The list is handed over whole and is never drawn here. What it looks like is the skin's, and drawing anything at all would put a decision in the headless layer that belongs one level up. → docs/rules/words-in-copy-looks-in-skin.md
Required scope
Dictation.Waveform must be inside <Dictation.Root>.
Props
2 documented props · Native <div> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { peaks: readonly number[]; speaking: boolean }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/waveform.tsx
Dictation.Speakinggate
Somebody is talking, rather than the room merely being on. Not the same as `Dictation.Listening`, which is about the microphone: an open microphone in a quiet room is listening and nobody is speaking. The difference is what a screen needs to say "go on, I'm getting this" without saying it into silence. The timing that makes it watchable — a syllable turns it on, a pause between words does not turn it off — is in `hooks/transcription/level.ts`.
Required scope
Dictation.Speaking must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/speaking.tsx
Dictation.Partialcontent · <span>
The hypothesis being revised, which never goes into the field. Shown beside it instead, so a person watching their own words appear is not also watching them rewrite themselves inside something they might be about to edit. Split at the last word, because that is the one that is about to be rewritten and a hypothesis that looks as settled as the text beside it is a hypothesis nobody reads as one. Which word is the unstable one is a fact; drawing it lighter is a look. → docs/rules/words-in-copy-looks-in-skin.md
Capability: live, partials
Required scope
Dictation.Partial must be inside <Dictation.Root>.
Props
2 documented props · Native <span> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { text: string; settled: string; last: string }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/partial.tsx
Dictation.Elapsedcontent · <span>
How long it has been listening, as m:ss.
Required scope
Dictation.Elapsed must be inside <Dictation.Root>.
Props
2 documented props · Native <span> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { elapsedMs: number }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/elapsed.tsx
Dictation.Enginecontent · <span>
Who is transcribing, and on which model. A dictation that fell back to the browser's own recogniser looked exactly like one on a provider until the words came out wrong; this is where it says which it is. Hidden until the instance has answered, and wherever it never does. → docs/rules/capability-honesty.md
Required scope
Dictation.Engine must be inside <Dictation.Root>.
Props
2 documented props · Native <span> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: TranscriptionEngine) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/engine.tsx
Dictation.Countdowncontent · <span>
How many seconds of quiet are left before it stops itself. Rendered only while something is counting, because that is the only time it is true — and it is worth saying out loud rather than only acting on: a dictation that ends in the middle of a thought reads as one that broke, and somebody who is pausing to think has no way to know a word would cancel it. → hooks/transcription/use-silence.ts
Required scope
Dictation.Countdown must be inside <Dictation.Root>.
Props
2 documented props · Native <span> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { seconds: number }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/countdown.tsx
Dictation.Idlegate
Nothing is happening: no microphone open, nothing waiting to be decided. Where a hint about how to start belongs. It is a gate rather than the absence of the others because "idle" is four of the seven states, and a pattern that wrote the negation itself would be a pattern reading the state. → docs/rules/states-gate-themselves.md
Required scope
Dictation.Idle must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/idle.tsx
Dictation.Listeninggate
A microphone is open.
Required scope
Dictation.Listening must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/listening.tsx
Dictation.Finalizinggate
Stopped, and waiting for the definitive text. Its own state because the wait is real and worth showing: without `live` it is the whole transcription, and a button that looked idle here would be pressed again.
Required scope
Dictation.Finalizing must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/finalizing.tsx
Dictation.Revisedgate
The definitive text disagrees with what is in the field. Only reached when somebody typed while it was listening — otherwise the better text is simply taken and nobody sees this. Here, they choose. → hooks/transcription/types.ts `diff` is what changed, word by word, because the alternative is asking somebody to read two sentences and spot the difference — which for the correction this usually is, one name in the middle of a line they have already read, is the whole decision hidden. What a removed or an added word looks like is the skin's. → hooks/transcription/word-diff.ts
Capability: live, file
Required scope
Dictation.Revised must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { revision: string; committed: string; diff: readonly DiffPart[] }) => ReactNode) | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/revised.tsx
Dictation.AcceptRevisionaction · <button>
Takes the machine's version.
Capability: live, file
Required scope
Dictation.AcceptRevision must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/accept-revision.tsx
Dictation.KeepMineaction · <button>
Keeps what is in the field.
Capability: live, file
Required scope
Dictation.KeepMine must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/keep-mine.tsx
Dictation.Cancelaction · <button>
Gives up and puts the field back. Not the same as the microphone button: that one finishes and waits for the text, this one throws it away. A dictation needs both. → services/transcription/ports.ts
Required scope
Dictation.Cancel must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/cancel-dictation.tsx
Dictation.Retryaction · <button>
Sends the kept recording again. Rendered only where there is one. A live session's words arrived as text or not at all, so on that path there is nothing behind this button and it is not drawn — which is the difference between offering a retry and offering a shape. → hooks/transcription/use-dictation.ts
Required scope
Dictation.Retry must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/retry-dictation.tsx
Dictation.DiscardRecordingaction · <button>
Throws the kept recording away, and with it the offer to send it again.
Required scope
Dictation.DiscardRecording must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/discard-recording.tsx
Dictation.Undoaction · <button>
Removes what dictation wrote, and nothing anybody typed.
Required scope
Dictation.Undo must be inside <Dictation.Root>.
Props
2 documented props · Native <button> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/undo-dictation.tsx
Dictation.MicDeniedgate
The browser will not give this page a microphone. Three refusals rather than one, because only one of them is a setting: no device, an insecure page or a browser without `getUserMedia` is nobody's to grant; a refusal can be taken back in the address bar; and a microphone another application is holding needs nothing changed at all, only the other application closed. Which one it was is read from the rejection `getUserMedia` threw, not from the Permissions API — Firefox has no `microphone` name and Safari's answer is unreliable, so a gate that waited for that one never appeared outside Chrome. → services/transcription/microphone.ts
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { unavailable: boolean; busy: boolean }) => ReactNode) | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/mic-denied.tsx
Dictation.Devicesinput · <select>
Which microphone to listen to. A `<select>`, which is the one control that is already the right thing on both ends: a menu beside a pointer and the operating system's own wheel or sheet on a phone, with its keyboard, its type-ahead and its screen-reader behaviour already correct. A popover built out of buttons would be a worse version of all of that on the smaller screen. The list is handed over and the options are drawn one level up, because an `<option>` is a host element and this layer renders none. What the labels say is the browser's — they are data rather than words, and are empty until permission has been given once, which is it declining to be a fingerprint rather than a bug. Three things a picker has to be honest about: - Before that first permission it is also the request. There is one device to list and no names for it, so it is shown anyway and opening it is what asks; the names arrive when the browser has said yes. Hiding it until then meant the first press always used the default, whatever somebody had meant to pick. - While listening it shows the microphone that is actually open, which is not always the one that was chosen, and it is disabled: a change then would be a choice that nothing acts on until the next press, and a select that looks live and is not is worse than one that says so. - Otherwise it is rendered only where there is a choice to make: one microphone and a picker beside it is a control that cannot do anything.
Required scope
Mic.Devices must be inside a transcription root.
Props
2 documented props · Native <select> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { devices: MicDevice[]; active: MicDevice | null; listening: boolean }) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/mic-devices.tsx
Dictation.Reconnectinggate
The recogniser dropped and is coming back. Its own gate because it is not a failure and not idleness: Chrome's recogniser ends itself after a silence even when it was told to be continuous, and a screen with only "listening" to show left somebody talking into a socket that had closed. The contract has modelled `reconnecting` from the start and nothing was reading it. → services/transcription/ports.ts
Capability: live, partials
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/reconnecting.tsx
Dictation.Statuscontent · <p>
The one region a screen reader is told about, and the reason it is always here. A live region has to exist *before* the text arrives in it: one that mounts with its content already in place announces nothing, which is why this is not simply an `aria-live` on the hypothesis. So it renders whatever a pattern puts in it at every state, empty included, and assistive technology watches one place from the start. What it says is the committed text, never the hypothesis. A partial is rewritten three or four times a second and reading each version out loud is not dictation feedback, it is a machine talking over the person. → components/headless/transcription/partial.tsx
Required scope
Dictation.Status must be inside <Dictation.Root>.
Props
2 documented props · Native <p> attributes supported
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: Announced) => ReactNode) | — |
renderReplace the default element with your own while retaining the part’s behavior and accessibility. | ReactElement | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/dictation-status.tsx
Dictation.Failedgate
The service refused, with the reason. Never a microphone: that is the gate beside this. `recorded` says whether the audio that pass was made from is still in hand, which decides whether there is anything to offer beyond an apology: a recorded pass can be sent again, and a live session's words arrived as text or not at all. → components/headless/transcription/retry-dictation.tsx `message` is the service's own words for it, `null` where it had none. Say it as written when it is there: a sentence picked from `error` alone is a guess about which refusal this was, and it was once wrong about a provider's 429.
Required scope
Dictation.Failed must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { error: TranscriptionErrorSlug; message: string | null; recorded: boolean }) => ReactNode) | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/dictation-failed.tsx
Dictation.NotLivegate
This instance cannot transcribe as somebody speaks. The button still works — it records and submits at the end — so this is a note about *when* the text will appear, not a refusal. A capability that is absent removes an affordance; it never breaks the screen. → docs/rules/capability-honesty.md Only where there is an "at the end" to speak of: an instance that cannot take the recording either has no button to make one with, and this would promise a transcript.
Required scope
Dictation.NotLive must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/not-live.tsx
Dictation.MissingConfigurationgate
The transcription backend answered that its server lacks a key it needs. Pinned to this family's service so a screen does not repeat the scope, and so another service's missing variable is never its notice. Where the instance can do without the key — a microphone that hides itself when no engine is available — nothing is asked of this gate: the server's warning is what says so. → docs/rules/missing-configuration-is-said.md
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { scopes: readonly string[] }) => ReactNode) | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/missing-configuration.tsx
Dictation.Availablegate
Draws its children only where `Dictation.MicButton` would draw one. It exists so what surrounds the button goes with it: a wrapper left behind an absent button is an empty element that still takes its place in a row's gap. → docs/rules/capability-honesty.md
Required scope
Dictation.Available must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/available.tsx
Dictation.NothingHeardgate
The dictation finished and no word came out of it. A quiet room, a microphone nobody spoke into, a session that closed with nothing said: all end `done`, and a screen that announced "inserted" over an empty field would be saying something untrue. → docs/rules/capability-honesty.md
Required scope
Dictation.NothingHeard must be inside <Dictation.Root>.
Props
1 documented props
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/nothing-heard.tsx
Dictation.Supportsgate
Draws its children only where this instance can do the thing. The gate exists because capability belongs to the configured instance rather than to the provider: the same key with a different model has different answers, and a screen that assumed would show a speaker column full of nothing. → services/transcription/capabilities.ts
Required scope
Transcript.Supports must be inside a transcription root.
Props
2 documented props
| Prop | Type | Default |
|---|---|---|
capabilityRequired | "file" /** Text arrives while somebody is still speaking. */ | "live" /** Revisable hypotheses, not just stable segments. */ | "partials" /** Text can be placed in time, which is what makes a transcript clickable. */ | "timestamps" /** Voices can be told apart. */ | "diarization" /** The language need not be stated in advance. */ | "language-detection" /** * Somebody may change language mid-sentence and be followed. * * Its own capability and not a corollary of `language-detection`, because most models * that detect do it once for the whole recording: they settle on a language and * transcribe the rest of a bilingual meeting in it, which is a worse answer than saying * they cannot. → docs/rules/capability-honesty.md */ | "code-switching" /** Business words and proper nouns can be given in advance. */ | "keywords" /** The transcript can come back in another language. */ | "translation" | — |
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/supports.tsx
Dictation.Lacksgate
Draws its children only where this instance *cannot* do the thing. The other half of `Supports`, and it is not the same screen with the condition flipped: a capability that is absent usually removes an affordance silently, and the cases worth a sentence are the ones where somebody would otherwise be surprised later — a model that hears one language at a time, given a list of three. Saying it is the alternative to pretending. → docs/rules/capability-honesty.md
Required scope
Transcript.Lacks must be inside a transcription root.
Props
2 documented props
| Prop | Type | Default |
|---|---|---|
capabilityRequired | "file" /** Text arrives while somebody is still speaking. */ | "live" /** Revisable hypotheses, not just stable segments. */ | "partials" /** Text can be placed in time, which is what makes a transcript clickable. */ | "timestamps" /** Voices can be told apart. */ | "diarization" /** The language need not be stated in advance. */ | "language-detection" /** * Somebody may change language mid-sentence and be followed. * * Its own capability and not a corollary of `language-detection`, because most models * that detect do it once for the whole recording: they settle on a language and * transcribe the rest of a bilingual meeting in it, which is a worse answer than saying * they cannot. → docs/rules/capability-honesty.md */ | "code-switching" /** Business words and proper nouns can be given in advance. */ | "keywords" /** The transcript can come back in another language. */ | "translation" | — |
childrenContent rendered inside this part. | ReactNode | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/transcription/src/headless/lacks.tsx
BarVisualizercontent · Generic
A few bars that rise with the voice, for the places a scrolling strip will not fit. Bands rather than moments: inside a mic button there is no room for a history, so the shape is the symmetric silhouette of one instant — the lowest band in the middle, the highest at the edges — which reads as "a voice" at sixteen pixels where a waveform reads as a smudge. Written to the elements' own styles from an animation frame, never through React: there are five of them and they change sixty times a second.
Props
5 documented props
| Prop | Type | Default |
|---|---|---|
sourceRequired | VoiceSignal | null | — |
stateWhat is happening, which decides the resting animation when nobody is talking. | "idle" | "connecting" | "listening" | "thinking" | "idle" |
bars | number | 5 |
size | keyof typeof SIZES | "md" |
className | string | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/voice-canvas/src/bar-visualizer.tsx
LiveWaveformcontent · Generic
The last couple of seconds of voice, scrolling right to left. A history rather than a spectrum, and the difference matters: a bar per frequency band is a picture of the sound, a bar per moment is a picture of the *speaking* — and it is the second one people read, because it is the only one that can show a pause. Every number it draws comes from the signal it was handed, read inside its own animation frame. It never renders, never sets state and never opens a microphone.
Props
7 documented props
| Prop | Type | Default |
|---|---|---|
sourceRequired
| VoiceSignal | null | — |
mode
| "idle" | "listening" | "processing" | "listening" |
barWidth | number | 2 |
gap | number | 2 |
sampleMsOne new bar every | number | 50 |
className | string | — |
aria-label | string | — |
Required props are marked. A dash means no explicit default is declared in this component.
Source: @slices.sh/voice-canvas/src/live-waveform.tsx
When something is missing
- No service configuration
- Install the transcription setup below and import "@/services". Required service roots report missing setup; gates do not replace that configuration.
- Missing capability or parent scope
- Follow each part’s context requirements and the example composition. Capability-aware controls hide when the provider cannot perform their action.