Dictation Card

Speech lands as live, editable text in a card inside the composer well, with explicit ✓/✕ — a review step instead of auto-send on silence. The card is a raised key in the entry well: a draft token not yet committed.

Dictation

Speech lands as editable text — nothing sends on silence.

tsx
"use client"

import * as React from "react"

import { DictationCard } from "@/registry/seam/ui/dictation-card"

const PHRASES = [
  "Refactor the auth middleware",
  "Refactor the auth middleware to use the new session store",
  "Refactor the auth middleware to use the new session store and add tests",
]

// Simulated dictation: the transcript grows while a synthetic level drives
// the visualizer. In a real app, feed your STT stream into `value` and the
// mic track into `track`.
export default function DictationCardDemo() {
  const [value, setValue] = React.useState(PHRASES[0])
  const [level, setLevel] = React.useState(0.4)
  const [status, setStatus] = React.useState<string | null>(null)

  React.useEffect(() => {
    let tick = 0
    const iv = setInterval(() => {
      tick++
      setLevel(0.25 + Math.abs(Math.sin(tick / 2)) * 0.6)
      if (tick % 4 === 0) {
        setValue(PHRASES[Math.min(tick / 4, PHRASES.length - 1)])
      }
    }, 500)
    return () => clearInterval(iv)
  }, [])

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      {/* the composer well the card sits in */}
      <div className="bg-muted shadow-well rounded-lg squircle border border-border/60 p-2">
        <DictationCard
          value={value}
          onValueChange={setValue}
          listening
          level={level}
          onConfirm={(v) => setStatus(`Confirmed: “${v.slice(0, 40)}…”`)}
          onCancel={() => {
            setValue("")
            setStatus("Discarded")
          }}
        />
      </div>
      <p className="text-muted-foreground text-xs" aria-live="polite">
        {status ?? "Speech lands as editable text — nothing sends on silence."}
      </p>
    </div>
  )
}
bunx --bun @seamui/cli@latest add dictation-card

Notes

  • Transport-agnostic: no STT engine inside. Stream your transcript into value, the mic level into level (or hand the visualizer a track); get onConfirm(value) / onCancel out. Confirm stays disabled while the transcript is empty.
  • The transcript is a real textarea — focusable, editable, announced as “Dictated text (editable)” — inside a labeled “Dictation” group. The visualizer keeps its role="status" state label, and its motion (with the reduced-motion variant) comes from voice-visualizer.
  • Pairs with the voice suite: media-toggle for the mic key, voice-control-bar for the call surface — this card is only the transcription moment.

Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.