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

# Insert transcripts into your editor

> Splice dictated segments into plain-text and rich editors at the caret, with correct spacing and casing

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/d6WKOaS9_Ts?start=134&end=229" title="Insert Transcripts into Your Editor — 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 [Rich text insertion example](https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/dictation-richtext) in `corti-examples` — bootstrap your build from it or use it as reference.
</Info>

The `/transcribe` API emits **plain transcript segments** — it has no knowledge of your editor's cursor, selection, or surrounding characters. Your application is responsible for splicing each segment into the target field with correct spacing and casing, at the caret.

This guide shows the **integration pattern**: how to take the transcript events the [Dictation Web Component](/sdk/dictation/overview) emits and insert them into both a plain `<textarea>` and a formatted (`contenteditable`) editor.

<Note>
  For the spacing- and casing-boundary **rules** themselves — when to add a space, when to capitalize, locale-specific handling — see [Transcript Text Handling](/stt/best-practices-transcribe). This page focuses on wiring those rules into a real editor.
</Note>

***

## Listen for transcript events

The Web Component emits a `transcript` event for each segment. Preview interim segments (`isFinal: false`) outside the document, and commit only final segments (`isFinal: true`) into the editor. See the [rich text insertion example](https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/dictation-richtext) in `corti-examples` for the full event wiring.

<Tip>
  Keep a single active interim span and replace it as newer interim results arrive. Do not accumulate interim results into committed text — see [Handling interim vs final segments](/stt/best-practices-transcribe#handling-interim-vs-final-segments).
</Tip>

***

## Write insertion logic once with an adapter

Insertion shouldn't care whether the target is a `<textarea>`, a `contenteditable` surface, or a native control reached through a host bridge. Define a small imperative seam — an **editor adapter** — and write the spacing/casing logic once against it.

```ts title="editor-adapter.ts" theme={null}
export interface EditorAdapter {
  getText(): string;
  getSelection(): { start: number; end: number };
  /** Insert a segment at the caret, applying spacing/casing rules. */
  insert(segment: string, options?: InsertionOptions): void;
  readonly richText: boolean;
  focus(): void;
}
```

Each concrete adapter implements the same surface, so the same dictation wiring drives any control.

***

## Splice into a plain-text field

For a `<textarea>`, read the selection, build the string to insert with the boundary rules, and replace the selected range.

```ts title="textarea adapter" theme={null}
insert(segment, options) {
  const { start, end } = getSelection();          // selectionStart / selectionEnd
  const insertion = buildInsertion(el.value, start, segment, options);
  el.value = el.value.slice(0, start) + insertion + el.value.slice(end);
  const caret = start + insertion.length;
  el.setSelectionRange(caret, caret);
}
```

`buildInsertion` applies the spacing/casing boundary rules from [Transcript Text Handling](/stt/best-practices-transcribe#spacing-rules-at-the-insertion-boundary) — pass `primaryLanguage` so locale-specific spacing (e.g. French non-breaking spaces) is handled, and set `capitalize: false` when `automaticPunctuation` is doing casing server-side.

***

## Splice into a rich-text editor

For `contenteditable`, insert a text node at the caret through the `Range`/`Selection` API. Because the node is inserted at the cursor, it **inherits any bold/italic formatting active there**. The boundary rules are computed against the plain text *before* the caret, so spacing and casing behave exactly as in the plain-text case.

```ts title="contenteditable adapter" theme={null}
insert(segment, options) {
  const sel = window.getSelection();
  if (!sel || sel.rangeCount === 0) return;

  const { start } = getSelection();
  const before = (el.textContent ?? "").slice(0, start);
  const insertion = buildInsertion(before, before.length, segment, options);

  const range = sel.getRangeAt(0);
  range.deleteContents();                         // replace any active selection
  range.insertNode(document.createTextNode(insertion));
  range.collapse(false);                          // caret after inserted text
}
```

<Note>
  The `contenteditable` here is a minimal stand-in — the reusable part is the STT integration (the adapter plus the boundary rules), not the editor. A production app using Tiptap, Lexical, or ProseMirror would implement the same `EditorAdapter` interface against its own document model; the insertion logic stays identical.
</Note>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Transcript Text Handling" icon="text-size" href="/stt/best-practices-transcribe">
    The full spacing, casing, and interim/final rules behind `buildInsertion`.
  </Card>

  <Card title="Replacements" icon="arrow-right-arrow-left" href="/stt/guides/dictation-replacements">
    Apply custom text substitutions to dictated output.
  </Card>

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

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