TranscriptAudioEditor
StableEdit recorded speech by editing its words: remove words to cut the audio, or change words to make new audio in a consented voice.
Also known as text based audio editor, transcript editor
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Edit a podcast intro
0:00 / 0:35 · Ready
No edits yet.
Select words to edit them. Shift and the arrow keys select more. Delete removes, D changes words, Enter keeps new audio.
Nothing selected. Tap or click a word to select it.
const [spans, setSpans] = useState<TranscriptEditSpan[]>([]);
<TranscriptAudioEditor
words={words}
spans={spans}
speakers={{ nadia: { label: "Nadia Brooks" }, tomas: { label: "Tomás Ferreira" } }}
audio={{ title: "The Allotment Hour, episode 12", duration: 24, peaks }}
voiceName="each speaker's own, consent on file"
limits={{ maxChars: 250, languages: ["English"] }}
onRemove={(ids) => setSpans((s) => [...s, { id: newId(), wordIds: ids, state: "removed" }])}
onRegenerate={(ids, text) => startVoiceJob(ids, text)} // adds a "regenerating" span
onKeep={(id) => setState(id, "kept")}
onRetry={(id) => startVoiceJob(id)}
onRevert={(id) => setSpans((s) => s.filter((x) => x.id !== id))}
onUndo={undo}
undoLabel="Undo: removed 2 words"
/>Press Play to hear it; the word being played gets a tint. Click a word, Shift-click another, then Remove or Change words. New audio takes a moment, then waits for Keep or Revert. Remove filler words cuts every marked filler at once, and Undo takes back each step.
When to use it
Lets someone fix a voice-over, podcast or interview by editing text instead of cutting a waveform, and see exactly which words the AI re-spoke.
Made of: Docked player · Waveform with cuts and new audio · Edit summary · Remove filler words · Undo · Keyboard hint · Speaker and time · Words · Edit point · New audio bar (Play, Retry, Keep, Revert) · Selection bar · New words field
Use it for
- Cutting filler words, false starts and long pauses from a recording.
- Fixing a wrong word in a voice-over without recording it again, using a voice the speaker agreed to.
- Reviewing which parts of a finished recording were made by AI before it is published.
Not for
- Reading along with a call recording, without editing: use TranscriptSync.
- Choosing between several takes of a whole clip: use TakePicker.
- Checking and recording a speaker's consent: use VoiceConsentCheck.
Do and avoid
- Removed, skipped when playing, 0:01 to 0:02
- New audio by AI, 0:04 to 0:05
Edited: 1 cut, 1 changed phrase. Length 0:10, was 0:11.
Select words to edit them. Shift and the arrow keys select more. Delete removes, D changes words, Enter keeps new audio.
Nothing selected. Tap or click a word to select it.
Nadia Brooks
So we started keeping seeds from the best plants
Do
- Keep removed words visible, struck through, so people can restore them.
- Wait for Keep before treating new audio as final, and keep the recording until then.
- Say why Change words is off, such as no consent or too many characters.
- Make every edit undoable.
Avoid
- Don't use the AI colour for the playing word or the person's own speech. Only new audio made by AI gets it.
- Don't use amber for new audio waiting to be kept. Nothing is wrong, and Keep is not urgent.
- Don't make new audio in a voice without the speaker's recorded consent.
- Don't announce each word as it plays. It would talk over the audio.
On a phone
- It fills the width. The player, waveform and words stack in one column.
- A tap selects a word; the − and + buttons in the selection bar make the selection shorter or longer, so no dragging is needed.
- Lines are 44px tall on a touch screen, and every button in the bars is 44px tall.
- The selection bar sticks to the bottom of the editor, so it stays in reach and sits just above the keyboard while typing new words.
- The keyboard shortcuts hint is hidden on narrow screens.
Seen in products
- Descript edits audio by editing its transcript; deleted words are cut, removed text can be set to be ignored, and cuts show as dashed boundaries.
- Descript Regenerate makes new audio for selected words, which pulses while it works and offers Keep or Revert; it is English only and capped at 250 characters.
- ElevenLabs Studio shows which paragraphs have been converted and offers Restore generation.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| wordsrequired | TranscriptWord[] | none | Each word: id, text, start and end in seconds, and optional speaker and filler. |
| spans | TranscriptEditSpan[] | [] | Edits so far: id, wordIds, state ("original" | "removed" | "regenerating" | "regenerated" | "kept" | "reverted" | "failed"), and optional newText and error. |
| speakers | Record<string, { label: string }> | none | Names for the speaker keys. Each run of words by one speaker is a paragraph. |
| audio | Omit<AudioPlayerProps, "current" | "onTimeChange" | "compact" | ...> | none | Wires in a docked player. The part keeps the time, skips removed words and plays new phrases. |
| peaks | number[] | null | none | Loudness for the waveform when you keep your own player. |
| duration | number | none | Length in seconds. Defaults to the audio's duration or the last word's end. |
| position | number | none | Playback position in seconds, when your own player keeps it. |
| onSeek | (seconds: number) => void | none | Called when a word is clicked, the waveform is moved or Play is pressed on new audio. |
| onRemove | (wordIds: string[]) => void | none | Shows Remove and Remove filler words. Make a removed span. |
| onRegenerate | (wordIds: string[], newText?: string) => void | none | Shows Change words. Make a regenerating span, then set it to regenerated or failed. |
| onSmooth | (wordIds: string[]) => void | none | Shows Smooth edit when the selection includes a cut. |
| onKeep | (spanId: string) => void | none | Keep new audio. Also on Enter while the phrase has focus. |
| onRetry | (spanId: string) => void | none | Make the new audio again. |
| onRevert | (spanId: string) => void | none | Back to the recording. Also restores removed words. |
| onUndo | () => void | none | Shows Undo, and runs on Ctrl+Z or Cmd+Z in the editor. |
| undoLabel | string | "Undo" | What Undo will undo. Used as the button's name. |
| voiceAvailable | boolean | true | False when there is no consented voice. Change words then says why. |
| voiceName | string | none | Whose voice new audio uses, shown while typing new words and in the AI label. |
| voiceUnavailableReason | string | none | Why the voice cannot be used, in one line. |
| limits | { maxChars?: number; languages?: string[] } | none | The voice model's limits, shown before typing and checked against the selection. |
| label | string | "Transcript" | Name of the transcript region. |
| className | string | none | Extra classes on the wrapper. |
"Show code under the preview gives the code for the example on the stage."
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- removed
- Struck-through words that playback skips; the waveform hatches the same stretch. They stay visible so they can be restored.
- regenerating
- A phrase being spoken again in a consented voice: a moving lime underline and Making new audio. Text only under reduced motion.
- regenerated
- New AI audio, marked with a lime underline and an AI label, with Play, Retry, Keep and Revert.
- kept
- New audio the person accepted; the AI mark stays so it is always clear which words were made.
- failed
- A dashed danger underline and the reason in words, with Retry and Revert.
- filler words
- Words such as um and like get a neutral dotted underline and one button removes them all.
- change not allowed
- When a phrase cannot be changed (no consented voice, crosses a cut, two speakers, too long) the reason is written in one line.
Tokens
Change one of these and every part that uses it follows.
--fg--fg-muted--fg-subtle--surface--surface-raised--surface-sunken--border--lime--danger-fg--radius-card--radius-field
Built in
- Each paragraph is a multi-select listbox named by speaker and time, and every word is an option. The whole transcript is one tab stop.
- Arrow keys move by word, Up and Down by paragraph, Home and End to the paragraph's ends, Ctrl+Home and Ctrl+End to the whole transcript's. Shift extends the selection and Space selects one word.
- Delete removes the selection, D opens Change words, Enter keeps new audio (or moves the playhead to the word), Escape clears the selection, Ctrl+Z undoes. Shortcuts are off while typing in the new words field.
- Removed, filler, making new audio, new audio by AI, kept and failed are all said in words after each word, never by colour or strike-through alone.
- Change words stays focusable when it is off, and its reason is linked to it as a description.
- A status region announces edits and finished or failed audio. The word being played is never announced.
- The waveform is a seek slider with the time in words, and lists cuts and new audio in its description.
- While new audio is made, its underline moves only when motion is allowed; the words Making new audio are always shown.
What you need to do
- Only set voiceAvailable when the speaker has consented to their voice being used, and say why in voiceUnavailableReason when they have not.
- Keep an undo history and pass onUndo with an undoLabel that says what it will undo.
- Pass the voice model's real limits in limits, so people learn them before they type.
- Label exported audio as partly AI-made when any span is kept.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide