# VoiceInput

> A mic button that opens into a recording panel: a live waveform painted from amplitude levels you supply, a stopwatch, the transcript as it arrives, and a cross and a check to throw it away or keep it.

- Package: `fancy-ui-svelte` (Svelte 5 + Tailwind CSS v4)
- Category: AI Chat
- Tags: ai, chat, voice, waveform, input
- Docs: https://fancy-ui.rama.app/docs/components/voice-input
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/src/lib/fancy-ui/voice-input

## Import

```svelte
<script lang="ts">
  import { VoiceInput } from 'fancy-ui-svelte';
</script>
```

Setup (once per project): `npm install fancy-ui-svelte`, then add `@import "fancy-ui-svelte/tailwind.css";` after `@import "tailwindcss";` in your main CSS file. Full guide: https://fancy-ui.rama.app/llms.txt

React: `import { VoiceInput } from 'fancy-ui-react';` (same prop names; `class` becomes `className`, snippets become `ReactNode`).

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Whether a recording is in progress (bindable). Every button writes through it; setting it from outside opens or closes the panel without firing a callback |
| `transcript` | `string` | `""` | Live text pushed by the consumer as their recogniser produces it, rendered muted under the waveform; a growing string grows in place |
| `samples` | `ArrayLike<number>` | — | Amplitude levels in 0..1 bridged from your own audio pipeline, one entry per bar. The component never opens a microphone itself |
| `demo` | `boolean` | `false` | Draw a deterministic synthetic wave when no samples arrive, so the panel is legible on a page with no audio behind it. Real samples always win |
| `onStart` | `() => void` | — | Called when the mic button starts a recording |
| `onStop` | `() => void` | — | Called when the check ends the recording and keeps the transcript |
| `onCancel` | `() => void` | — | Called when the cross abandons the recording |
| `height` | `number` | `48` | Waveform height in CSS pixels, clamped to 16..240 |
| `color` | `string` | `var(--ft-voice-color, currentColor)` | Any CSS colour for the bars, including a var() or currentColor — written onto the canvas as its CSS color and read back resolved, because a 2D context understands neither |
| `sound` | `boolean` | `false` | Plays open on start, close on cancel and select on a confirmed recording, once the user has enabled sound |

## Examples

### Basic Usage

```svelte
<script lang="ts">
	import { onMount } from "svelte";
	import { VoiceInput } from "fancy-ui-svelte";

	/** How fast the pretend recogniser hands back another word. */
	const WORD_MS = 400;
	const WORDS = "show me the retry policy for failed webhook deliveries".split(" ");

	let active = $state(false);
	let transcript = $state("");

	// Plain, not reactive: it is the rig's cursor into WORDS, not something the
	// markup reads.
	let spoken = 0;

	function reset() {
		spoken = 0;
		transcript = "";
	}

	onMount(() => {
		// A transcript that types itself is exactly the motion this setting asks us
		// not to run, so it gets the finished state instead: the panel already open,
		// the sentence already said, and no timer of any kind.
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
			active = true;
			transcript = WORDS.join(" ");
			return;
		}

		// One interval for the life of the example rather than one per recording:
		// it reads `active` each tick and does nothing until the mic is pressed.
		const timer = setInterval(() => {
			if (!active || spoken >= WORDS.length) return;
			transcript = transcript ? `${transcript} ${WORDS[spoken]}` : WORDS[spoken];
			spoken += 1;
		}, WORD_MS);
		return () => clearInterval(timer);
	});
</script>

<div class="flex w-full max-w-md flex-col gap-3 p-6">
	<!--
		`demo` draws the synthetic waveform, because this page has no microphone
		behind it. A real integration drops that and passes `samples` from its own
		audio pipeline instead.
	-->
	<VoiceInput sound bind:active {transcript} demo onStop={reset} onCancel={reset} />

	<p class="text-muted-foreground text-xs">
		Press the mic and the words arrive as a recogniser would hand them over. The check keeps the
		transcript and closes the panel; the cross throws it away.
	</p>
</div>
```
