wide-elephant-63050
02/22/2026, 2:13 PMtall-airline-16756
02/22/2026, 2:13 PMtall-airline-16756
02/22/2026, 2:14 PMuseTranscriptions hook from @livekit/components-react to receive real-time transcription segments published to the lk.transcription text-stream topic. The hook returns structured transcription segments (including participant identity, text, and whether segments are interim or final) that are ready to render in your UI.
Example:
import { useTranscriptions } from '@livekit/components-react';
function ChatDisplay() {
const transcriptions = useTranscriptions();
return (
<div>
{transcriptions.map((segment) => (
<div key={segment.id}>
<strong>{segment.participant?.identity}:</strong> {segment.text}
</div>
))}
</div>
);
}
A couple of quick notes to ensure it works:
• The room or agent must publish transcriptions to the lk.transcription topic (e.g., an STT agent or speech-to-text integration).
• The hook differentiates interim vs final segments so you can display live updates and finalize text when appropriate.
If you want, I can:
• Show how to filter by participant identity or only show final segments.
• Share an example that debounces interim updates to reduce UI flicker.
Which of those would be most helpful?
Sources: Text and transcriptions | LiveKit Documentation | Client SDKs | livekit/livekit_composite | DeepWiki | How to build an agent with speech-to-text input and text-only output | Live...wide-elephant-63050
02/22/2026, 2:16 PMtall-airline-16756
02/22/2026, 2:16 PMidentity).
import React from 'react';
import { useTranscriptions } from '@livekit/components-react';
function ParticipantTranscripts({ identity }: { identity: string }) {
const transcriptions = useTranscriptions();
const filtered = transcriptions.filter(
(seg) => seg.participant?.identity === identity
);
return (
<div>
{filtered.map((seg) => (
<div key={seg.id}>
<strong>{seg.participant?.identity}:</strong> {seg.text}
</div>
))}
</div>
);
}
2) Show only final segments and debounce interim updates
• Use this when you want a stable UI and avoid flashing interim text. This example displays final segments immediately and updates interim text with a short debounce.
import React, { useEffect, useMemo, useState } from 'react';
import { useTranscriptions } from '@livekit/components-react';
function FinalOnlyWithDebouncedInterim({ debounceMs = 300 }) {
const transcriptions = useTranscriptions();
// Store finalized segments separately
const finalized = useMemo(
() => transcriptions.filter((s) => s.isFinal),
[transcriptions]
);
// Track last interim per participant and show them with debounce
const [interimMap, setInterimMap] = useState<Record<string, string>>({});
useEffect(() => {
let timers: Record<string, number> = {};
transcriptions.forEach((seg) => {
if (!seg.isFinal) {
const id = seg.participant?.identity ?? seg.id;
// clear existing timer for this identity
if (timers[id]) {
window.clearTimeout(timers[id]);
}
// schedule update after debounceMs
timers[id] = window.setTimeout(() => {
setInterimMap((m) => ({ ...m, [id]: seg.text }));
}, debounceMs);
} else {
// remove interim display when final arrives
const id = seg.participant?.identity ?? seg.id;
setInterimMap((m) => {
const copy = { ...m };
delete copy[id];
return copy;
});
}
});
// cleanup timers on unmount or re-run
return () => {
Object.values(timers).forEach((t) => window.clearTimeout(t));
};
}, [transcriptions, debounceMs]);
return (
<div>
<div>
{finalized.map((seg) => (
<div key={seg.id}>
<strong>{seg.participant?.identity}:</strong> {seg.text}
</div>
))}
</div>
<div style={{ opacity: 0.7 }}>
{Object.entries(interimMap).map(([identity, text]) => (
<div key={identity}>
<strong>{identity} (listening):</strong> {text}
</div>
))}
</div>
</div>
);
}
Notes and tips
• The hook listens to the lk.transcription text stream; ensure your agent or STT integration publishes there.
• Use seg.participant?.identity to attribute segments; fallback to seg.id if identity is missing.
• Adjust debounceMs to balance responsiveness vs UI stability.
• If you need to persist transcripts, collect the segments into state or send them to your backend when seg.isFinal is true.
Would you like a ready-to-drop example that also saves final segments to local state or uploads them to a server? Which option fits your use case: single-speaker transcription, multi-speaker with attribution, or saving transcripts for later?