—kit

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.json

Add @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

PropTypeDefaultDescription
state"idle" | "listening" | "processing" | "done" | "error"What the component reports. Announced politely on change.
meterLevelMeter | nullFrom useMicrophone. Read every frame, written to CSS variables, zero renders.
streamMediaStream | nullAlternative source. Listening creates and disposes its own meter.
levelnumberManual 0..1. Shows SOURCE [MANUAL] and never a dB value. Re-renders on change.
ref.setLevel(level: number, db?: number) => voidImperative, frame-rate safe. dB shows only if you pass one.
ticksnumber24 (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.
processingSincenumberclock() time processing began. Shows real elapsed time while processing.
capturedMsnumberCaptured duration, shown in done. Omitted means no CAPTURED reading.
errorKind"denied" | "unsupported" | "failed""denied"MIC [DENIED], MIC [UNAVAILABLE] or MIC [ERROR].
clock() => numberperformance.nowTimebase for processingSince.

# Presets

Input, idle<Listening state="idle"><textarea /></Listening>
MicListening
Pill, manual<Listening geometry="pill" state="listening" level={0.5} />
MIC [DENIED]Microphone access denied
Denied<Listening state="error" errorKind="denied" />
Done
Done, captured<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 })