Xzero UI
Pro library

Speech playerPro

A read-aloud player for AI replies. The Read aloud button in the message's action row turns into a compact player with play, a scrubbable progress bar, speed and a voice picker, and a highlight follows the spoken word through the reply, karaoke style.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/speech-player
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/speech-player

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { SpeechPlayer } from "@/components/xzero/speech-player/speech-player";

export function AssistantMessage({ message }) {
  return (
    <SpeechPlayer
      variant="plain"
      text={message.text}
      author={{ name: "Claude Sonnet 5.5", mark: <ClaudeMark style={{ color: "#D97757" }} /> }}
      voices={[
        { id: "ava", name: "Ava", description: "Warm and conversational" },
        { id: "leo", name: "Leo", description: "Calm, lower register" },
      ]}
      synthesize={async ({ text, voice, signal }) => {
        const res = await fetch("/api/tts", { method: "POST", body: JSON.stringify({ text, voice: voice.id }), signal });
        if (!res.ok) throw new Error("The voice service is busy. Try again in a moment.");
        const { audioUrl, words } = await res.json(); // words: [{ start, end }] in ms
        return { src: audioUrl, timings: words };
      }}
      onPlay={e => track("tts_play", { voice: e.voice.id, speed: e.speed })}
      actions={<><CopyButton value={message.text} iconOnly variant="plain" /><RegenerateButton /></>}
    />
  );
}

// With no props it reads the sample reply, simulated, with no audio.
<SpeechPlayer />

API reference

SpeechPlayer

Read aloud for an AI reply. The reply sits above an action row with Read aloud, Copy, Good response and Bad response. Read aloud crossfades the row into a compact player of the same height: a play and pause button, the elapsed time, a thin scrubbable progress bar, the total time, a speed button (1×, 1.5×, 2×), a voice picker and Stop reading. As it plays, the spoken word is highlighted in the paragraph by one rounded rect that glides from word to word along a line and cuts cleanly to the next line, while words still to come stay dimmed. Clicking a word jumps there. It covers idle, preparing (a shimmering "Preparing Ava's voice"), playing, paused, switching voice mid-sentence (it resumes from the same word), finished (Play again) and failed (Retry). With no props it reads a sample travel reply from Claude Sonnet 5.5 once in view and loops through two voices until someone interacts.

PropTypeDefaultDescription
textstring–The reply to read. Blank lines start new paragraphs; each whitespace separated word is one highlight step. Leave it out for the self playing sample.
synthesize(request: { text: string; voice: SpeechVoice; signal: AbortSignal }) => Promise<SpeechAudio>–Fetches speech from your TTS provider. Resolve with { src, timings?, duration? }: src is an audio URL or object URL, and timings are word start and end times in milliseconds from the provider's alignment data. Without timings, they are estimated from the text and scaled to the audio length. Abort the request when the signal fires (a new voice or Stop reading). Leave synthesize out for simulated timing with no audio.
voicesSpeechVoice[]Ava, Leo, Maya, NoahVoices in the picker: { id, name, description? }. The description is one short line, such as "Warm and conversational".
voicestring–Selected voice id (controlled). Pair with onVoiceChange.
defaultVoicestringfirst voiceInitial voice id when uncontrolled.
onVoiceChange(voice: SpeechVoice) => void–Called when a voice is picked. While reading, the player fetches the new voice and resumes from the word being spoken.
speed1 | 1.5 | 2–Playback speed (controlled). Pair with onSpeedChange.
defaultSpeed1 | 1.5 | 21Initial speed when uncontrolled. The speed button cycles 1×, 1.5×, 2×.
onSpeedChange(speed: SpeechSpeed) => void–Called when the speed button is pressed.
onPlay(event: SpeechPlaybackEvent) => void–Playback started or resumed. The event carries time and duration in milliseconds on the 1× timeline, wordIndex, voice and speed.
onPause(event: SpeechPlaybackEvent) => void–Playback paused.
onEnd(event: SpeechPlaybackEvent) => void–The last word finished.
onSeek(event: SpeechPlaybackEvent) => void–A scrub, an arrow key or a click on a word moved playback. Fires once when a drag ends, not on every move.
onWordChange(event: SpeechWordEvent) => void–The spoken word changed. Adds word to the playback event; use it to sync a caption or analytics.
onClose() => void–Stop reading was pressed and the action row returned. Fetched audio is kept, so Read aloud starts again instantly.
author{ name: string; mark?: ReactNode } | null–Who wrote the reply, shown above it with a 16px mark. Defaults to Claude Sonnet 5.5 for the sample and to nothing when text is passed.
actionsReactNode–Buttons after Read aloud in the action row. Defaults to Copy, Good response and Bad response (feedback is local and announced as simulated); null shows Read aloud alone.
autoPlaybooleantrue for the sampleStarts reading once the player is in view. Browsers may block audio autoplay; the player then waits paused at 0:00.
simulatedbooleantrue without synthesizeNotes in the voice menu and in announcements that no audio plays.
variant"card" | "plain""card"card draws a bordered surface; plain drops border and padding to sit inside a chat column.
classNamestring–Class on the root element.

simulateSpeech

The stand in TTS request behind the preview: waits like a network round trip, then resolves with estimated word timings and no audio. Nothing is sent. Pass { fail: true } as the second argument to see the error state, or { delay } to tune the wait. estimateSpeechTimings(text, pace) and fitTimings(timings, durationMs) are exported too.

PropTypeDefaultDescription
request{ text: string; voice: SpeechVoice; signal: AbortSignal }–The same request synthesize receives.
options{ delay?: number; fail?: boolean }{ delay: 640 }Milliseconds before it resolves, and whether it rejects instead.

Keyboard interactions

KeysAction
TabMoves through Read aloud and the actions, or, while reading, through play, the progress bar, speed, voice and Stop reading.
Enter, SpaceOn Read aloud, opens the player and starts reading with focus on the play button. On play, plays or pauses; on Stop reading, closes the player and returns focus to Read aloud.
Arrow Left, Arrow RightOn the progress bar, moves 2 seconds back or forward. Page Up and Page Down move 10 seconds; Home and End jump to the start or end.
Space, KOn the progress bar, plays or pauses.
Arrow Up, Arrow DownIn the voice menu, moves between voices; Enter picks one and Escape closes the menu.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in ai

Guidance for AI tools

Speech player: A read-aloud player for AI replies. The Read aloud button in the message's action row turns into a compact player with play, a scrubbable progress bar, speed and a voice picker, and a highlight follows the spoken word through the reply, karaoke style. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components