Skip to content
SLICES / TECHNICAL FOLIO Nº 001FULL-STACK ASSEMBLIES — OPEN SOURCE

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.

Behavior / inputGateGenericRenderer · not headlessScope
COMPONENT ANATOMYHover · focus · tap
ONE POSSIBLE DESIGNStatic preview

Dictation.Root · Scope

Dictation.RootRoot 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.

Installation

Choose what your component needs. Run these commands in an initialized Next.js app.

0

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 nova

Or start fresh: the slices CLI creates the app and sets it up.

npx slices.sh install
1

Install 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 services
2

Install the headless library

npm

Add the React parts to your app, then compose them with your own elements.

npm install @slices.sh/transcription@^0.1.11

Import from @slices.sh/transcription/headless.

3

Install the service

Connect the parts to a provider. Skip this step if this service is already configured in your app.

Provider
npx add transcription.browser

Adds 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
0

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 nova

Or start fresh: the slices CLI creates the app and sets it up.

npx slices.sh install
1

Install the headless library

npm

Add the React parts to your app, then compose them with your own elements.

npm install @slices.sh/voice-canvas

Import 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.

example.tsx
"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

Dictation.Root props
PropTypeDefault
enabled

Left out or true: required, and a missing service throws slices/service-missing. "optional": renders its children with no transcription service, and the parts degrade: Dictation.Field renders its input untouched and the microphone renders nothing. false: off. A capability name ("live") requires the service and that capability.

Enabled—
config

The transcription config, "default" when left out.

string—
value

The 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[]—
maxDurationMs

A ceiling: a microphone left open bills without producing anything.

number—
stopAfterSilenceMs

How long a quiet has to last before it finishes on its own. 0 never does. The one failure here that costs money quietly is a microphone somebody walked away from. → hooks/transcription/use-silence.ts

number0
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

booleanfalse
revisions

Offer the definitive text when it disagrees with an edit. Off, for a screen with no Revised part: the edit stands, rather than a choice nobody can see being announced. → hooks/transcription/use-dictation.ts

booleantrue
children

Content 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

Dictation.Field props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.Marks props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { before: string; run: string; after: string; at: Marked }) => ReactNode)—
render

Replace 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

Dictation.MicButton props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { listening: boolean }) => ReactNode)—
render

Replace 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

Dictation.MicLevel props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { level: number }) => ReactNode)—
render

Replace 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

Dictation.Level props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { level: number; speaking: boolean }) => ReactNode)—
render

Replace 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

Dictation.Signal props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { source: SignalSource | null; mode: "idle" | "listening" | "processing" }) => ReactNode)—
render

Replace 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

Dictation.Waveform props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { peaks: readonly number[]; speaking: boolean }) => ReactNode)—
render

Replace 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

Dictation.Speaking props
PropTypeDefault
children

Content 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

Dictation.Partial props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { text: string; settled: string; last: string }) => ReactNode)—
render

Replace 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

Dictation.Elapsed props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { elapsedMs: number }) => ReactNode)—
render

Replace 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

Dictation.Engine props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: TranscriptionEngine) => ReactNode)—
render

Replace 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

Dictation.Countdown props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { seconds: number }) => ReactNode)—
render

Replace 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

Dictation.Idle props
PropTypeDefault
children

Content 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

Dictation.Listening props
PropTypeDefault
children

Content 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

Dictation.Finalizing props
PropTypeDefault
children

Content 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

Dictation.Revised props
PropTypeDefault
children

Content 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

Dictation.AcceptRevision props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.KeepMine props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.Cancel props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.Retry props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.DiscardRecording props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.Undo props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace 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

Dictation.MicDenied props
PropTypeDefault
children

Content 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

Dictation.Devices props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { devices: MicDevice[]; active: MicDevice | null; listening: boolean }) => ReactNode)—
render

Replace 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

Dictation.Reconnecting props
PropTypeDefault
children

Content 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

Dictation.Status props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: Announced) => ReactNode)—
render

Replace 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

Dictation.Failed props
PropTypeDefault
children

Content 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

Dictation.NotLive props
PropTypeDefault
children

Content 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

Dictation.MissingConfiguration props
PropTypeDefault
children

Content 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

Dictation.Available props
PropTypeDefault
children

Content 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.Unavailablegate

An instance is installed and cannot take a microphone here: the browser's recogniser in a browser that has none, say. The place for a sentence saying so, because a button that is simply absent leaves somebody looking for one. Not drawn where no instance is installed at all: that screen chose not to dictate, and has nothing to explain. → docs/rules/capability-honesty.md

Required scope

Dictation.Unavailable must be inside <Dictation.Root>.

Props

1 documented props

Dictation.Unavailable props
PropTypeDefault
children

Content 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/unavailable.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

Dictation.NothingHeard props
PropTypeDefault
children

Content 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

Dictation.Supports props
PropTypeDefault
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"—
children

Content 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

Dictation.Lacks props
PropTypeDefault
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"—
children

Content 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

BarVisualizer props
PropTypeDefault
sourceRequired
VoiceSignal | null—
state

What is happening, which decides the resting animation when nobody is talking.

"idle" | "connecting" | "listening" | "thinking""idle"
bars
number5
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

LiveWaveform props
PropTypeDefault
sourceRequired

null before a microphone is open, which draws the resting baseline.

VoiceSignal | null—
mode

listening scrolls the voice, processing freezes and collapses it, idle rests.

"idle" | "listening" | "processing""listening"
barWidth
number2
gap
number2
sampleMs

One new bar every sampleMs; the bars glide between samples, so the scroll is smooth.

number50
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.
Read the setup guide →