Listening
Turns any child into a level meter. The ticks, the peak and the dB are the microphone, measured.
# Preview
Source [mic]
Prompt
# Install
shell
npx shadcn@latest add https://kit.ulasalyesil.com/r/listening.jsonAdd @import "../styles/hud.css"; to your global CSS, after @import "tailwindcss". Zero dependencies beyond React.
# Usage
tsx
import { Listening } from "@/components/ui/listening";
import { useMicrophone } from "@/hooks/use-microphone";
const mic = useMicrophone();
<Listening
state={mic.status === "live" ? "listening" : mic.status === "error" ? "error" : "idle"}
meter={mic.meter}
errorKind={mic.permission === "denied" ? "denied" : "unsupported"}
>
<textarea aria-label="Prompt" />
</Listening>
<button onClick={() => void mic.start()}>[ Start mic ]</button># Props
| Prop | Type | Default | Description |
|---|---|---|---|
| state | "idle" | "listening" | "processing" | "done" | "error" | What the component reports. Announced politely on change. | |
| meter | LevelMeter | null | From useMicrophone. Read every frame, written to CSS variables, zero renders. | |
| stream | MediaStream | null | Alternative source. Listening creates and disposes its own meter. | |
| level | number | Manual 0..1. Shows SOURCE [MANUAL] and never a dB value. Re-renders on change. | |
| ref.setLevel | (level: number, db?: number) => void | Imperative, frame-rate safe. dB shows only if you pass one. | |
| ticks | number | 24 (12 for pill) | Tick count on the -60 to 0 dBFS scale. Every fourth tick is a 10 dB mark at 24. |
| geometry | "input" | "pill" | "screen" | "input" | Wrap a block, sit inline, or frame the viewport. |
| processingSince | number | clock() time processing began. Shows real elapsed time while processing. | |
| capturedMs | number | Captured duration, shown in done. Omitted means no CAPTURED reading. | |
| errorKind | "denied" | "unsupported" | "failed" | "denied" | MIC [DENIED], MIC [UNAVAILABLE] or MIC [ERROR]. |
| clock | () => number | performance.now | Timebase for processingSince. |
# Presets
<Listening state="idle"><textarea /></Listening>MicListening
<Listening geometry="pill" state="listening" level={0.5} />MIC [DENIED]Microphone access denied
<Listening state="error" errorKind="denied" />Done
<Listening state="done" capturedMs={2400} /># Agent prompt
Paste into Claude Code, Cursor or any coding agent to wire it in.
prompt
Add the —kit Listening component to this project.
1. Run: npx shadcn@latest add https://kit.ulasalyesil.com/r/listening.json
2. Make sure the global CSS imports styles/hud.css after tailwindcss.
3. Import: import { Listening } from "@/components/ui/listening";
4. Props:
- state: "idle" | "listening" | "processing" | "done" | "error". What the component reports. Announced politely on change.
- meter: LevelMeter | null. From useMicrophone. Read every frame, written to CSS variables, zero renders.
- stream: MediaStream | null. Alternative source. Listening creates and disposes its own meter.
- level: number. Manual 0..1. Shows SOURCE [MANUAL] and never a dB value. Re-renders on change.
- ref.setLevel: (level: number, db?: number) => void. Imperative, frame-rate safe. dB shows only if you pass one.
- ticks: number. Tick count on the -60 to 0 dBFS scale. Every fourth tick is a 10 dB mark at 24.
- geometry: "input" | "pill" | "screen". Wrap a block, sit inline, or frame the viewport.
- processingSince: number. clock() time processing began. Shows real elapsed time while processing.
- capturedMs: number. Captured duration, shown in done. Omitted means no CAPTURED reading.
- errorKind: "denied" | "unsupported" | "failed". MIC [DENIED], MIC [UNAVAILABLE] or MIC [ERROR].
- clock: () => number. Timebase for processingSince.
5. Truth rule: only pass values you actually have. Leave a prop out rather than
passing a placeholder; its readout will not render.
6. With the Vercel AI SDK: npx shadcn@latest add https://kit.ulasalyesil.com/r/ai-sdk.json
then: const turn = useKitTurn({ message: lastAssistant, status })