MicButton
StableThe one mic control for dictation and starting voice, with every mic state written next to it: recording, locked, turning speech into text, blocked and no mic.
Also known as microphone button, record button, push to talk
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Dictate into a message box
Your words go into the box. Nothing is sent until you send it.
const [state, setState] = useState<MicState>("idle");
const level = useMicLevel(state === "recording"); // your analyser, 0 to 1
<div className="flex items-end gap-2 rounded-field border border-line bg-surface p-2">
<textarea value={text} onChange={(e) => setText(e.target.value)} rows={2} />
<MicButton
state={state}
level={level}
label="Dictate"
onStart={async () => { setState("requesting"); setState(await startRecorder() ? "recording" : "blocked"); }}
onStop={async () => { setState("processing"); setText(await transcribe()); setState("idle"); }}
/>
</div>Tap once to start and once to stop. The button waits for permission, records, then writes the words into the box. The person still sends it.
When to use it
Gives people one clear, labelled control for the mic, so they always know whether it is listening. The state is in words beside the button, not only in an icon or a colour.
Made of: Round button · Mic icon, level bars, stop mark or spinner · Attention dot (blocked only) · State word · Hold hint · Cancel (locked only) · Live region (hidden)
Use it for
- Dictating into a message box: the speech fills the field and the person sends it.
- Starting a voice conversation from a composer.
- Hold-to-talk voice notes on a phone, with slide up to lock and slide away to cancel.
Not for
- Muting the mic during a live voice session: use VoicePanel.
- Playing back a recording.
- A plain toggle that is not about the mic: use Switch.
Do and avoid
Do
- Keep the transcribed words in the field for the person to read and send.
- Show blocked with a way to fix it, not a dead button.
- Use lg for the main control on a voice screen, md inside a composer.
- Give a typed route next to the mic.
Avoid
- Do not colour the recording state lime. Lime is for the AI; this is the person's voice.
- Do not use amber for recording. Amber is only for blocked, where the person has to act.
- Do not rely on the system mic dot alone to show the mic is on.
- Do not start recording without a press.
On a phone
- The button is 44px (md) or 56px (lg), so it meets the touch size without extra padding.
- Hold mode stops the page from scrolling or opening the long-press menu while the finger is down.
- Slide up 56px to lock, or slide more than 32px off the button and let go to cancel. The words beside the button say which will happen.
- Start and stop give a short buzz on phones that support it. Turn it off with haptics={false}.
Seen in products
- Claude: dictation fills the message box and the person sends it; push-to-talk holds Space in Claude Code's /voice.
- Gemini Live: Mute, Hold and End are separate, labelled controls.
- Messaging apps: hold to record a voice note, slide up to lock and slide away to cancel.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| staterequired | "idle" | "requesting" | "recording" | "locked" | "processing" | "blocked" | "unavailable" | none | Where the mic is. The app sets it from the real permission, recorder and transcriber. |
| mode | "tap" | "hold" | "tap" | tap: press to start, press again to stop. hold: hold to talk, slide up to lock, slide away to cancel. |
| level | number | 0 | Input level from 0 to 1. Drives the three bars while recording. |
| onStartrequired | () => void | none | The person asked to start. Ask for the mic, then set requesting, recording or blocked. |
| onStoprequired | () => void | none | The person asked to stop. Keep what was said. |
| onCancel | () => void | none | Throw the recording away. Called when a held press slides off and lets go, or Cancel is pressed while locked. |
| onLock | () => void | none | Hold mode: the person slid up. Set state to locked. |
| onHelp | () => void | none | Pressed while blocked. Open the steps to turn the mic on. |
| label | string | "Dictate" | What the button does when idle, such as Dictate or Start voice. It is the accessible name when idle. |
| showLabel | boolean | false | Show the label as text beside the button when idle. Other states always show their word. |
| size | "md" | "lg" | "md" | md is 44px. lg is 56px, for the main control on a voice screen. |
| haptics | boolean | true | A short buzz on start and stop where the device supports it. |
| className | string | none | Classes for the outer row. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one MicButton switches all of this from its properties panel, across 112 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| Status | state | Idle, Requesting, Recording, Locked, Processing, Blocked, Unavailable |
| Show label | showLabel | On or off |
| Size | size | MD, LG |
| State | Hover, Pressed and Focus come from the browser. | Default, Hover, Pressed, Focus |
| Label | label | Any text |
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- permission prompt open
- Set state to requesting while the browser asks. The ring turns and the word says Allow the mic to start.
- cancel armed
- Hold mode: while the finger is more than 32px off the button, the bars give way to a stop mark and the word says Let go to cancel.
Tokens
Change one of these and every part that uses it follows.
--accent--on-accent--surface--line--line-strong--fg--fg-muted--danger--attention--dur-fast--dur-instant--ease-out--touch-target
Built in
- It is a real button. Its name changes with the state: the label when idle, Stop recording while recording, and Mic blocked. Show how to turn it on when blocked.
- aria-pressed is true while recording or locked.
- Requesting, processing and unavailable set aria-disabled, so the button stays focusable and its name explains why it does nothing.
- A polite status region says only Recording and Stopped, once each.
- In hold mode, holding Space records and releasing stops; Enter starts and stops like a toggle. No key cancels, so nothing is thrown away by one key.
- Every state shows a word beside the button, so the state never depends on colour or the icon alone.
- Under reduced motion the level bars are replaced by a still stop mark and the press does not shrink.
What you need to do
- Set state from what is really happening: requesting while the permission prompt is open, blocked when it was refused, unavailable when there is no input device.
- Pass onHelp when the mic can be blocked, and open steps that say how to turn it back on.
- Put the transcribed words in the field for the person to check. Never send dictation on their behalf.
- Pass level from a real analyser, from 0 to 1. Leave it at 0 if you have none; the word Recording still shows.
- Offer a typed route next to it. Voice must never be the only way in.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide