Push to Talk
ProA hold-to-talk key for radios, voice notes and walkie-talkie chat. Live exactly while held, by pointer, keyboard or a global key, with a halo everyone reads as on air and a shake for a press too short to send.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Shapes#
circle (the default) is the icon alone, for a console where the key sits beside other controls; name it with aria-label and show that name in a Tooltip. pill says it in words and, while live, counts the time on air. Hold each one.
Sizes#
sm is 32px, for a dense board: it still takes a 40px press (44px on touch) through an invisible hit box, and its halo and shake travel less. md (the default) is 40px, the hit target on its own; lg is 48px, for the main control of a console or a phone's bottom bar. The glyph steps with the size.
Variants#
The idle paint. neutral (the default) is the ink solid: a transmit key is a tool, not a brand moment. primary takes the brand, for a product whose main action is talking. outline is a quiet bordered key, for a board where every row has one. Live is the same for all three: the destructive fill and the halo.
Dispatch board#
A talk key in every unit cell. size="sm" with variant="outline" keeps the board calm at rest; the key held goes live as loudly as any other. Give each one its own aria-label, the unit it calls.
Minimum duration#
A press shorter than minDuration (450ms by default) is a slip, not a message. It fires onTalkCancel instead of onTalkEnd, both with the duration, and the control shakes. Here the minimum is 600ms. minDuration={0} turns the check off: every hold goes out and nothing shakes.
Global shortcut#
shortcut makes a key transmit while held anywhere on the page, the way a radio console works. It stands down while focus is in a text field, while a modifier is held and while a dialog the control is not in is open. The key also lands in aria-keyshortcuts.
Transmitting from a hotkey#
When the transmit is handled elsewhere (a game's global push-to-talk key, a hardware button, a state pushed from the server), pass it as live: the key shows on air, with the destructive fill, the halo and, on the pill, the timer counting from when it turned on, without being pressed. It is a visual state only: it fires no callback, so the code that owns the transmit keeps owning it, and the pointer and keys still work on their own hold. Here the page listens for B and the keys only mirror it.
Labels and icons#
label and liveLabel set the words; timer={false} drops the count. icon and liveIcon swap the glyphs, which cross-fade on the live state.
Disabled#
disabled dims the control and turns its shortcut off. Disabling it while live ends the hold as an interruption: onTalkEnd(ms) fires whatever the length, never a cancel and never the shake. Unmounting it while live does the same, so a closed panel or a switched tab cannot leave the channel open: whatever onTalkStart opened, onTalkEnd (or onTalkCancel for a slip) closes.
API reference#
PushToTalk
A hold-to-talk <button> with aria-pressed, live only while held. Forwards all native <button> props except children (your pointer and key handlers run first); accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| shape | "circle" | "pill" | "circle" | circle is icon only, named by label unless you pass aria-label; pill shows the words and, while live, the timer. |
| size | "sm" | "md" | "lg" | "md" | sm is 32px for a dense board, md 40px, lg 48px for a console's main control. |
| variant | "neutral" | "primary" | "outline" | "neutral" | The idle paint: the ink solid, the brand fill, or a hairline for dense boards. Live is always the destructive fill. |
| label | React.ReactNode | "Hold to talk" | The words at rest; on the circle, a string label becomes its aria-label. |
| liveLabel | React.ReactNode | "On air" | The words while live, before the timer (pill only). |
| timer | boolean | true | Show the time on air (m:ss) beside liveLabel (pill only). |
| minDuration | number | 450 | A hold shorter than this, in ms, is cancelled: onTalkCancel fires instead of onTalkEnd and the control shakes. |
| shortcut | string | - | A key (KeyboardEvent.key, case-insensitive) that transmits while held anywhere on the page. Ignored while typing, with a modifier held, or while a dialog the control is not in is open; also set as aria-keyshortcuts. |
| icon | React.ReactNode | <Microphone weight="fill" /> | The glyph at rest. |
| liveIcon | React.ReactNode | <Waveform weight="bold" /> | The glyph while live; the two cross-fade. |
| disabled | boolean | - | Dims the control and turns its shortcut off. Disabling it while live ends the hold. |
| live | boolean | - | Shows it on air from outside (a hotkey handled elsewhere), without a press or a callback. |
| onTalkStart | () => void | - | The hold began. |
| onTalkEnd | (durationMs: number) => void | - | The hold ended after at least minDuration: the message went out. Also fires, whatever the length, when a live control is disabled or unmounted. |
| onTalkCancel | (durationMs: number) => void | - | The hold ended before minDuration: nothing went out. |
| onTalkingChange | (talking: boolean) => void | - | Fires with true on start and false on any end, sent or cancelled. |
| Data attribute | Description |
|---|---|
| data-state | "idle" at rest, "live" while held. |
| CSS variable | Description |
|---|---|
| --shake-distance | How far the rejection shake travels. Set to 3px. |