Components / Audio
Transcript Viewer
Word-by-word transcript display synced to Audio Player. Pass word timings from your transcription pipeline; the current word highlights in lime as playback progresses. Click any word to seek the shared player.
Word-by-word playback
Timings are loaded from audio_test.words.json alongside the sample MP3. Compose TranscriptViewerContainer inside the same AudioPlayerProvider as AudioPlayerBar.
Timeline mode
Set variant="timeline" and pass breaks to group words into jumpable segments. Each break ends a segment after the given word index; optional label, speaker, and role metadata title each row in the list. Click a segment to jump; the active segment highlights as playback progresses.
import {
TranscriptViewerContainer,
TranscriptViewerTimeline,
} from "@/components/ui/transcript-viewer"
const breaks = [
{
id: "opening",
label: "Call to order",
speaker: "David Okonkwo",
role: "Chair",
afterWordIndex: 7,
},
]
<AudioPlayerProvider item={recording}>
<TranscriptViewerContainer words={wordTimings} breaks={breaks} variant="timeline">
<TranscriptViewerTimeline />
</TranscriptViewerContainer>
<AudioPlayerBar position="docked" label="Welcome recording" />
</AudioPlayerProvider>Word timing format
Each word needs a start and end time. By default timings are in milliseconds; pass timeUnit="s" if your ASR returns seconds.
{
"words": [
{ "word": "Welcome", "start": 0, "end": 347 },
{ "word": "to", "start": 347, "end": 440 },
{ "word": "here.", "start": 2032, "end": 2560 }
],
"breaks": [
{
"id": "opening",
"label": "Call to order",
"speaker": "David Okonkwo",
"role": "Chair",
"afterWordIndex": 7
},
{
"id": "overview",
"label": "Platform overview",
"speaker": "Sarah Mitchell",
"role": "Strata manager",
"afterWordIndex": 28
}
]
}Installation
Requires audio-player to be installed first.
npx shadcn add https://raw.githubusercontent.com/levi-putna/elements/main/registry/transcript-viewer/registry.jsonUsage
import { AudioPlayerBar, AudioPlayerProvider } from "@/components/ui/audio-player"
import {
TranscriptViewerContainer,
TranscriptViewerWords,
} from "@/components/ui/transcript-viewer"
const recording = { id: "welcome", src: "/audio/welcome.mp3" }
const wordTimings = [
{ word: "Welcome", start: 0, end: 347 },
{ word: "to", start: 347, end: 440 },
// ...
]
<AudioPlayerProvider item={recording}>
<TranscriptViewerContainer words={wordTimings}>
<TranscriptViewerWords />
</TranscriptViewerContainer>
<AudioPlayerBar position="docked" label="Welcome recording" />
</AudioPlayerProvider>Reference
TranscriptViewerContainer
| Prop | Type | Default | Description |
|---|---|---|---|
| words | TranscriptWordTiming[] | - | Word text with start and end timings from transcription or forced alignment. |
| breaks | TranscriptTimelineBreakpoint[] | [] | Timeline segment boundaries. Each break ends a segment after the given word index. Optional speaker and role metadata display above the passage text. |
| finalSegment | TranscriptFinalSegmentMeta | - | Optional label, speaker, and role for the trailing segment after the last break. |
| variant | "words" | "timeline" | "words" | Inline word highlighting or grouped timeline segments. |
| timeUnit | "ms" | "s" | "ms" | Unit for each word start and end value. |
TranscriptViewerWords
| Prop | Type | Default | Description |
|---|---|---|---|
| renderWord | ({ word, status }) => ReactNode | - | Optional custom renderer per word. |
TranscriptViewerTimeline
| Prop | Type | Default | Description |
|---|---|---|---|
| renderSegment | ({ segment, status }) => ReactNode | - | Optional custom renderer per timeline segment. |