Voice Control Bar
The capstone of the voice suite — a floating call pill that holds the media controls and the hang-up key, and morphs open into a chat composer. It's where Media Toggle, Device Selector, and Composer come together into one control.
A split mic control, camera/screen keys, a chat toggle, and the hang-up key.
tsx
"use client"
import * as React from "react"
import { MessageSquare } from "lucide-react"
import {
VoiceControlBar,
VoiceControlBarActions,
VoiceControlBarTrigger,
VoiceControlBarEnd,
} from "@/registry/seam/ui/voice-control-bar"
import {
DeviceSelector,
DeviceSelectorContent,
DeviceSelectorTrigger,
} from "@/registry/seam/ui/device-selector"
import { MediaToggle } from "@/registry/seam/ui/media-toggle"
const MICS = [
{ deviceId: "default", label: "MacBook Pro Microphone" },
{ deviceId: "airpods", label: "AirPods Pro" },
]
// The resting call pill: a split mic control, camera and screen keys, a chat
// toggle, and the hang-up key — all riding in one raised, rounded pill.
export default function VoiceControlBarDemo() {
const [mic, setMic] = React.useState("default")
return (
<VoiceControlBar>
<VoiceControlBarActions>
<div className="bg-muted flex items-center gap-1 rounded-full p-1 shadow-well">
<MediaToggle kind="mic" defaultPressed className="size-8" />
<DeviceSelector devices={MICS} value={mic} onValueChange={setMic}>
<DeviceSelectorTrigger />
<DeviceSelectorContent />
</DeviceSelector>
</div>
<MediaToggle kind="camera" />
<MediaToggle kind="screen-share" />
<VoiceControlBarTrigger>
<MessageSquare className="size-4" />
</VoiceControlBarTrigger>
<VoiceControlBarEnd />
</VoiceControlBarActions>
</VoiceControlBar>
)
}bunx --bun @seamui/cli@latest add voice-control-barAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| expanded | boolean | — | Controlled expanded state of the panel. |
| defaultExpanded | boolean | false | Uncontrolled initial state; the Trigger flips it. |
| onExpandedChange | (expanded: boolean) => void | — | Called when the panel opens or closes. |
Props on the root <VoiceControlBar>; Trigger and End accept all Button props.
Notes
- The bar owns no transport — drop in whatever call controls you like and wire
VoiceControlBarEndto your disconnect. It defaults to an “End call” label when it renders icon-only. - The morph is the one sanctioned duration case: the panel opens with the grid-rows
0fr → 1frheight trick while the container's radius and padding transition together — pill (rounded-full) to card (rounded-3xlsquircle). Under reduced motion it snaps (motion-reduce:transition-none) rather than freezing mid-morph, while every control inside keeps its own press feedback. - The trigger drives
aria-expanded(andaria-pressed) with a label that flips with the panel, and the panel isaria-hiddenwhile collapsed so its contents stay out of the tab order.
Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.