> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a dictation box

> Dictate into a scratch box, transfer it into a form field, and navigate the form by voice — all on one mic

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/d6WKOaS9_Ts?start=477&end=553" title="Build a Dictation Box — walkthrough" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen />

<Info>
  **Follow along with the runnable example.** This guide walks through the [Dictation box example](https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/dictation-box) in `corti-examples` — bootstrap your build from it or use it as reference.
</Info>

A **dictation box** is a classic speech-enabled-app workflow: dictate into a scratch box, refine the text, then **transfer** it into whichever form field you were working in, and navigate the form by voice along the way. A single microphone drives the whole thing.

This guide combines two earlier building blocks: caret [Text Insertion](/stt/guides/dictation-text-insertion) through an `EditorAdapter`, and the [Commands](/stt/guides/dictation-commands) declare → event → dispatch loop. The new idea is **routing** — deciding *which* control each dictation segment lands in.

***

## The commands

The workflow is driven by a small command set: two to control routing, one to transfer, and two to navigate the form.

| Command                | Effect                                                          |
| :--------------------- | :-------------------------------------------------------------- |
| `show dictation box`   | Focus the scratch box                                           |
| `target dictation box` | Route dictation into the box **without** moving focus           |
| `transfer text`        | Insert the box's text into the last-active field, then clear it |
| `go to {field}`        | Focus a text field, or open a dropdown                          |
| `pick {number}`        | Select the Nth option in the open dropdown                      |

```ts title="JavaScript" theme={null}
const BOX_COMMANDS = [
  { id: "show_dictation_box",   phrases: ["show dictation box", "show the dictation box"] },
  { id: "target_dictation_box", phrases: ["target dictation box", "target the dictation box"] },
  { id: "transfer_text",        phrases: ["transfer text", "transfer the text"] },
  {
    id: "go_to_field",
    phrases: ["go to {field}", "go to {field} field"],
    variables: [{ key: "field", type: "enum", enum: ["note one", "note two", "severity"] }],
  },
  {
    id: "pick_option",
    phrases: ["pick {number}", "choose {number}", "option {number}"],
    variables: [{ key: "number", type: "enum", enum: ["one", "two", "three", "1", "2", "3"] }],
  },
];
```

***

## Route dictation to the right target

By default, final dictation inserts at the caret of **whatever control is focused** — exactly like typing. The one exception is the *box-target override*: after `target dictation box`, dictation goes into the scratch box while the form field keeps its focus and caret.

```ts title="JavaScript" theme={null}
// On each final segment, route to the box or the active field.
if (targetBox) {
  // Override: route into the scratch box; focus stays on the form field.
  boxAdapter.insert(segment, { primaryLanguage: "en" });
} else {
  // Default: insert at the caret of the focused control.
  activeAdapter.insert(segment, { primaryLanguage: "en" });
}
```

To make `transfer text` work, track the **last-active field that isn't the box** — that's the transfer destination. A `focusin` listener records it, and focusing any field clears the override.

```ts title="JavaScript" theme={null}
container.addEventListener("focusin", (e) => {
  const el = e.target;
  if (el === boxEl) return;              // ignore the box itself
  lastFieldRef.current = adapterFor(el); // remember the transfer destination
  if (targetBox) setTargetBox(false);    // focusing a field cancels the override
});
```

***

## Transfer the box into a field

`transfer text` inserts the box's contents at the caret of the last-active field — reusing the same boundary-aware `insert` — then clears the box and the override.

```ts title="JavaScript" theme={null}
function transfer() {
  const text = boxEl.value;
  if (!text) return;
  lastFieldRef.current?.insert(text, { primaryLanguage: "en" });
  boxEl.value = "";
  setTargetBox(false);
}
```

***

## Navigate the form by voice

`go to {field}` focuses a text field (caret to end) or opens a dropdown. `pick {number}` selects an option — parse the spoken value so both `"two"` and `"2"` resolve to the same 1-based index.

```ts title="JavaScript" theme={null}
const WORDS = ["one", "two", "three", "four", "five", "six"];

function parseOrdinal(value: string): number | null {
  const v = value.trim().toLowerCase();
  const digit = Number.parseInt(v, 10);
  if (Number.isFinite(digit) && digit > 0) return digit;
  const word = WORDS.indexOf(v);
  return word === -1 ? null : word + 1;
}
```

<Note>
  A native `<select>` can't be opened or selected-by-index programmatically, so dropdown fields need a custom listbox (a button + option list) exposing an imperative `open`/`pick` handle. Text fields work directly through the `EditorAdapter`.
</Note>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Text Insertion" icon="text-cursor" href="/stt/guides/dictation-text-insertion">
    The caret-insertion `EditorAdapter` that box and field writes reuse.
  </Card>

  <Card title="App Control" icon="wand-magic-sparkles" href="/stt/guides/dictation-app-control">
    Drive non-text UI — tabs, panels, dialogs — by voice.
  </Card>

  <Card title="Example code" icon="github" href="https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/dictation-box">
    The complete, runnable Dictation box example in `corti-examples`.
  </Card>
</CardGroup>

<Note>Please [contact us](mailto:help@corti.ai) for help or questions.</Note>
