PushToTalkButton
The round microphone button of the voice HUD. It toggles the mic and, while active, shows two
pulsing rings and a stronger glow so the user can see that JARVIS is listening.
import { PushToTalkButton } from 'jarvis-react-ui';
Toggle the microphone
The button is fully controlled: keep the mic state yourself, pass it as active and flip it in
onClick. onClick is a normal button click handler and receives the click event; a handler
without arguments such as () => setLive((v) => !v) works too. A dashed rim rotates slowly in both
states.
import { useState } from 'react';
import { PushToTalkButton } from 'jarvis-react-ui';
export default function Toggle() {
const [live, setLive] = useState(false);
return (
<div className="flex flex-col items-center gap-4">
<PushToTalkButton active={live} onClick={() => setLive((v) => !v)} />
<span className="text-[10px] uppercase tracking-[1px] text-text-muted">
Mic {live ? 'live' : 'off'}
</span>
</div>
);
}
Hold to talk
All native <button> attributes and events are forwarded, including disabled, id, ref and
the pointer and keyboard events. For walkie-talkie style input, turn the mic on in onPointerDown
and off in onPointerUp; also stop on onPointerLeave and onPointerCancel so the mic does not
stay live when the pointer slides off or the browser cancels the gesture. onKeyDown/onKeyUp for
Space give keyboard users the same hold behavior (ignore e.repeat while the key is held).
import { useState } from 'react';
import { PushToTalkButton } from 'jarvis-react-ui';
export default function HoldToTalk() {
const [live, setLive] = useState(false);
const start = () => setLive(true);
const stop = () => setLive(false);
return (
<div className="flex flex-col items-center gap-4">
<PushToTalkButton
active={live}
aria-label="Hold to talk"
onPointerDown={start}
onPointerUp={stop}
onPointerLeave={stop}
onPointerCancel={stop}
onKeyDown={(e) => {
if (e.key === ' ' && !e.repeat) start();
}}
onKeyUp={(e) => {
if (e.key === ' ') stop();
}}
/>
<span className="text-[10px] uppercase tracking-[1px] text-text-muted">
{live ? 'Listening · release to send' : 'Hold to talk'}
</span>
</div>
);
}
Custom content
children replace the default Mic icon. Mark the icon aria-hidden and describe the action with
aria-label, because the button has no visible text.
import { useState } from 'react';
import { Radio } from 'lucide-react';
import { PushToTalkButton } from 'jarvis-react-ui';
export default function CustomContent() {
const [open, setOpen] = useState(false);
return (
<PushToTalkButton
active={open}
onClick={() => setOpen((v) => !v)}
aria-label="Open radio channel"
>
<Radio size={24} strokeWidth={1.8} aria-hidden="true" />
</PushToTalkButton>
);
}
Warning: ariaLabel is deprecated
The
ariaLabelprop still works but is deprecated. Use the standardaria-labelattribute instead; when both are set,aria-labelwins.
With the orb state
In an assistant UI the button usually drives the orb state (AppOrbState) rather than a plain
boolean. Here a click moves idle to listening, a second click ends the turn (thinking), and the
reply plays out as speaking before returning to idle. A WaveformMeter
on each side runs while audio flows, and a StatusLabel names the state.
import { useEffect, useState } from 'react';
import { PushToTalkButton, StatusLabel, WaveformMeter } from 'jarvis-react-ui';
import type { AppOrbState } from 'jarvis-react-ui';
/** States that advance on their own: [next state, delay in ms]. */
const AUTO_NEXT: Partial<Record<AppOrbState, [AppOrbState, number]>> = {
thinking: ['speaking', 1200],
speaking: ['idle', 2400],
};
export default function VoiceLoop() {
const [state, setState] = useState<AppOrbState>('idle');
useEffect(() => {
const next = AUTO_NEXT[state];
if (next === undefined) return;
const id = setTimeout(() => setState(next[0]), next[1]);
return () => clearTimeout(id);
}, [state]);
function handleClick() {
setState((s) => (s === 'idle' ? 'listening' : s === 'listening' ? 'thinking' : s));
}
const audible = state === 'listening' || state === 'speaking';
return (
<div className="flex flex-col items-center gap-5">
<div className="flex items-center gap-6">
<WaveformMeter active={audible} />
<PushToTalkButton active={state === 'listening'} onClick={handleClick} />
<WaveformMeter active={audible} mirrored />
</div>
<StatusLabel state={state} />
</div>
);
}
For a global keyboard shortcut (for example Space anywhere on the page), listen for
keydown/keyup in your app and update active yourself; a Hint can advertise
the key.
Sound
Hovering plays hover_button and clicking plays click through the nearest SfxProvider. Without a
provider the button stays silent. Pointer and key handlers you pass do not play extra sounds. See
Sound effects.
Accessibility
- Renders a native
<button type="button">, so it is focusable and Enter and Space activate it.disableddisables it and dims it. aria-label(defaultPush to talk) is the accessible name; the icon is decorative. The deprecatedariaLabelprop is used only whenaria-labelis not set.aria-pressedmirrorsactive, so screen readers announce it as a toggle button that is pressed while the mic is live. Passaria-pressedyourself to override it.- The focus ring (2px accent outline) is visible on keyboard focus only (
focus-visible).
API
| Prop | Type | Default | Description |
|---|---|---|---|
active |
boolean |
false |
Whether the microphone is live; shows the pulse rings and a stronger glow, and sets aria-pressed. |
aria-label |
string |
'Push to talk' |
Accessible name of the button, rendered as aria-label. Wins over the deprecated ariaLabel. |
ariaLabel |
string |
Accessible name of the button. @deprecated Use the standard aria-label attribute instead; aria-label wins when both are set. |
|
children |
ReactNode |
Content of the button; replaces the default microphone icon when set. | |
className |
string |
Extra classes for the root <button> element. |
|
onClick |
MouseEventHandler<HTMLButtonElement> |
Called when the button is clicked, after the click sound plays. Handlers that take no arguments (() => void) work as well. |
Also accepts inherited props from ButtonHTMLAttributes (10), HTMLAttributes (54), AriaAttributes (52), DOMAttributes (168), RefAttributes (1), Attributes (1).