> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Voice Mode Fab - React Native

> The VoiceModeFab is the floating control for starting, monitoring, and ending Atlas voice mode.

> [!NOTE]
>
> For development, import `VoiceModeFab` from
> `@servicetitan/anvil2-rn`. For design, find it in the
> [Mobile Agentic Component Library file](https://www.figma.com/design/gjGWnAOnI1kukfzjsUQ6dP/Mobile-Agentic-Component-Library).

**Implementation**

```tsx theme={null}
import { VoiceModeFab } from "@servicetitan/anvil2-rn";

function App() {
  return <VoiceModeFab state="idle" onPress={startVoice} />;
}
```

## Common Examples

The component has three states, discriminated on `state`. The host owns all voice state and transport; `VoiceModeFab` renders the control and reports presses.

### Collapsed

`idle` is a plain dark circle — voice is available but not running. `active` means a conversation is running. Both take a single `onPress`.

The blue presence glow is **not** a function of the call being open: it marks Atlas holding the floor, so it renders only while `isAgentSpeaking` is `true`. A host that does not track speech gets a plain circle rather than a halo that is always on.

```tsx theme={null}
<VoiceModeFab state="idle" onPress={startVoice} />
<VoiceModeFab state="active" onPress={expand} />
```

### Expanded

The control strip: a status `label` plus mic-mute, stop, and collapse. Those three are required — each appears in every frame of the design, and an absent handler would render a control that looks live and does nothing. A speaker control is available on top, and renders only when the `isSpeakerMuted` / `onToggleSpeakerMuted` pair is wired — they are optional as a unit, never half-set.

```tsx theme={null}
<VoiceModeFab
  state="expanded"
  label="Talking"
  isMicMuted={micMuted}
  onToggleMicMuted={() => setMicMuted((m) => !m)}
  onStop={endVoice}
  onCollapse={() => setState("active")}
/>
```

Wiring `onToggleSpeakerMuted` adds the Atlas-audio control:

```tsx theme={null}
<VoiceModeFab
  state="expanded"
  label="Talking"
  isMicMuted={micMuted}
  onToggleMicMuted={() => setMicMuted((m) => !m)}
  isSpeakerMuted={speakerMuted}
  onToggleSpeakerMuted={() => setSpeakerMuted((m) => !m)}
  onStop={endVoice}
  onCollapse={() => setState("active")}
/>
```

> [!WARNING]
>
> **`onStop` and `onCollapse` are not interchangeable.** Collapsing returns to
> `active` with voice still running; stopping ends voice mode entirely. The
> two controls sit next to each other in the strip, so wiring them to the
> same handler is an easy mistake with very different consequences for the
> user.

`label` is caller-supplied so the host can localize it and reflect its own state ("Talking", "Listening", …). The kit owns only the action names.

## Accessibility

* Every control is a `button` with an explicit accessible name.
* The collapsed control's name changes with state — "Start voice mode" when `idle`, "Voice mode controls" when `active`. The glow is purely visual, so the name is the only signal a screen-reader user gets.
* The mute controls carry their state in their name ("Mute microphone" / "Unmute microphone") rather than a checked state — the two together would contradict, announcing an unmuted mic as "Mute microphone, not selected".
* Each mute control also shows its state visually: the glyph crosses out and its tint turns critical, so a muted channel is not announced-only.
* Built-in action labels are English until the kit gains an i18n layer; `label` is already caller-supplied.

**VoiceModeFab Props**

```tsx theme={null}
<VoiceModeFab state="active" onPress={expand} />
```

## `VoiceModeFab` Props

Discriminated on `state`. It does not forward remaining React Native props.

### Collapsed — `state: "idle" | "active"`

**state** `"idle" | "active"` *required*

`idle` = voice available, not running. `active` = listening, collapsed
(renders the presence glow).

**onPress** `() =\> void` *required*

Starts voice mode (from `idle`) or expands the control strip (from
`active`).

**isAgentSpeaking** `boolean` (default: false)

Whether Atlas is currently speaking. Drives the presence glow — the
design's "Atlas talking shadow" — so it marks whose turn it is rather than
that a call is open. Only read while `state` is `"active"`.

### Expanded — `state: "expanded"`

**state** `"expanded"` *required*

Renders the control strip instead of the circle.

**label** `string` *required*

Status text, e.g. "Talking". Caller-supplied so the host can localize it.

**isMicMuted** `boolean` *required*

Whether the user's microphone is muted. Controlled.

**onToggleMicMuted** `() =\> void` *required*

Fired when the mic-mute control is pressed.

**isSpeakerMuted** `boolean`

Whether Atlas's spoken output is muted. Controlled. Required together
with `onToggleSpeakerMuted` — the two are typed as a pair, so the control
can never render without the state that names it.

**onToggleSpeakerMuted** `() =\> void`

Fired when the Atlas-audio control is pressed. Wiring it adds that control
to the strip; without it the strip is mic, stop and collapse — the
canonical design.

**onStop** `() =\> void` *required*

Ends voice mode entirely.

**onCollapse** `() =\> void` *required*

Collapses back to `active` **without** ending voice mode.

### Shared

**analyticsId** `string`

Opt-in telemetry id. When set under `Anvil2RNTelemetryProvider`, each press
emits a `press` event with `meta.action` (`start`, `expand`,
`toggleMicMuted`, `toggleSpeakerMuted`, `stop`, `collapse`).

**testID** `string`

Test hook forwarded to the root element.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.