> ## 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.

# Prompt Bar - React Native

> The PromptBar provides a controlled composer for text, voice capture, and sending.

> [!NOTE]
>
> For development, import `PromptBar` 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 { useState } from "react";
import { PromptBar } from "@servicetitan/anvil2-rn";

function App() {
  const [value, setValue] = useState("");

  return (
    <PromptBar
      value={value}
      onChange={setValue}
      onSend={() => send(value)}
      placeholder="Ask anything…"
    />
  );
}
```

## Common Examples

To use the `PromptBar`, pass controlled `value` and `onChange`. The Send button follows three states:

| State | Editor | Send |
| - | - | - |
| At rest | unfocused, empty | not rendered |
| Composing | focused, still empty | present, disabled |
| Draft | any trimmed text | present, enabled |

At rest the action would have nothing to do, and a permanently-disabled arrow reads as decoration rather than an affordance — so the resting bar is the editor alone, plus the mic if you wired `onStartVoice`. Once the editor is focused the send action appears disabled, so its enabled/disabled state can show what makes a draft sendable. It also stays disabled if `onSend` is unwired, and sending is allowed when the draft has trimmed text unless you override `canSend`.

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

function ExampleComponent() {
  const [value, setValue] = useState("");

  return (
    <PromptBar
      value={value}
      onChange={setValue}
      onSend={() => send(value)}
    />
  );
}
```

The editor grows until `maxRows` (default `8`), then it scrolls.

### Stop

While `isSending` is true, sending is withheld. Wire `onStop` to surface Stop instead of Send.

```tsx theme={null}
<PromptBar
  value={value}
  onChange={setValue}
  isSending
  onStop={() => cancel()}
/>
```

### Voice

PromptBar supports two distinct, mutually exclusive voice flows. A host uses one or the other, never both.

#### Dictation (speak → transcribe → insert)

The kit does not record or transcribe. Wire `onStartVoice` to show a mic. Drive `voiceState` (`"idle"`, `"listening"`, `"transcribing"`) from the host. Pass `audioLevel` (0–1) while listening and `transcript` while transcribing. Wire `onAcceptVoice` and `onCancelVoice` to show those listening-row controls.

```tsx theme={null}
<PromptBar
  value={value}
  onChange={setValue}
  onStartVoice={startMic}
  voiceState="listening"
  audioLevel={0.6}
  onAcceptVoice={accept}
  onCancelVoice={cancel}
/>
```

#### Live conversation

For a two-way spoken conversation, set `isVoiceModeActive`. The bar collapses to a single control that returns the user to typing — the editor, mic, and send are all withheld, because one input mode is active at a time. The input surface goes with them: with no editor to frame, the exit control sits bare on the page rather than inside the bar's usual bordered, elevated pill. The spoken exchange renders as chat messages above, and the conversation's own controls (mute, stop, collapse) live in the floating [`VoiceModeFab`](https://anvil.servicetitan.com/docs/components/voice-mode-fab/react-native), not in the bar.

```tsx theme={null}
<PromptBar
  value={value}
  onChange={setValue}
  isVoiceModeActive
  onExitVoiceMode={endConversation}
/>
```

`isVoiceModeActive` and `onExitVoiceMode` are a **required pair** — the type will not let you set the flag without the handler. Collapsing the bar withholds the editor, mic and send, so a missing handler would leave the user with no route back to typing. The host ends the conversation and clears the flag.

> [!NOTE]
>
> If a host sets both `isVoiceModeActive` and a dictation `voiceState`, voice
> mode wins. That combination is a misconfiguration, but collapsing is the
> safer render: a listening row would offer an accept control that writes
> into an editor the user cannot see.

Attachments and the web waveform are not part of the React Native surface yet.

### Disclaimer

Pass a plain-string `disclaimer` to show fine print under the bar. Omit it to hide the line. Rich-text and link support are not ported.

```tsx theme={null}
<PromptBar
  value={value}
  onChange={setValue}
  onSend={() => {}}
  disclaimer="AI responses may be inaccurate."
/>
```

### Disabled

`disabled` disables the editor, send, and mic.

Built-in action labels (`sendLabel`, `stopLabel`, `startVoiceLabel`, and the voice-row labels) default to English. Override them for localized copy.

## Accessibility

* `placeholder` is the editor's accessible name.
* While listening, `listeningLabel` is announced through a polite live region because the level meter is decorative.
* While transcribing, `transcribingLabel` names the row until `transcript` arrives.
* Send, Stop, mic, accept, and cancel use the corresponding label props as accessible names.

**PromptBar Props**

```tsx theme={null}
<PromptBar
  value={value}
  onChange={setValue}
  onSend={() => send(value)}
  placeholder="Ask anything…"
/>
```

## `PromptBar` Props

The `PromptBar` accepts the following props. It does not forward remaining React Native `View` or `TextInput` props.

**onChange** `(value: string) =\> void` *required*

Fired on every edit with the next value.

**value** `string` *required*

Current draft text. Controlled.

**acceptVoiceLabel** `string` (default: Accept voice input)

Accessible name for the listening-row accept control.

**analyticsId** `string`

Opt-in telemetry id. When set under `Anvil2RNTelemetryProvider`, send
emits a `submit` event with `meta.length` (character count only, never
the draft text) before `onSend`.

**audioLevel** `number`

Input level from 0 to 1 for the listening meter. Clamped. Only read
while `voiceState` is `"listening"`.

**canSend** `boolean`

Override for whether sending is allowed. Defaults to a non-whitespace
draft. Setting it `true` also surfaces the Send button on an otherwise
empty, unfocused bar, so a host that decides sendability by other means is
not left without the action.

**cancelVoiceLabel** `string` (default: Cancel voice input)

Accessible name for the listening-row cancel control.

**disabled** `boolean`

Disables the editor and the send action.

**disclaimer** `string`

Fine-print sentence below the bar. Omit to hide it.

**isSending** `boolean`

Withholds sending while a response is in flight. Pair with `onStop` to
surface Stop.

**listeningLabel** `string` (default: Recording)

Polite live-region status while `voiceState` is `"listening"`.

**maxRows** `number` (default: 8)

Rows the editor grows to before it starts scrolling.

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

Accepts the current voice capture. Surfaces the accept control in the
listening row.

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

Discards the current voice capture. Surfaces the cancel control in the
listening row.

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

Fired when Send fires. The Send button stays disabled until this is
wired.

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

Fired when the mic action fires. Wiring it surfaces the mic. The host
owns recording and speech-to-text.

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

Fired when Stop fires. Only surfaces while `isSending` is true.

**placeholder** `string`

Placeholder for the empty editor. Also the editor's accessible name.

**sendLabel** `string` (default: Send)

Accessible name for the send action.

**startVoiceLabel** `string` (default: Start voice input)

Accessible name for the mic button.

**stopLabel** `string` (default: Stop)

Accessible name for Stop while `isSending` is true.

**testID** `string`

Test hook forwarded to the root view.

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

Whether a live voice conversation is running. Collapses the bar to the
exit-to-typing control; the editor, mic, and send are withheld. Distinct
from `voiceState`, which drives dictation — a host uses one or the other.

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

Leave voice mode. Fired by the control shown while `isVoiceModeActive`.
Required whenever that flag is set — the two are typed as a pair.

**exitVoiceModeLabel** `string` (default: Switch to keyboard)

Accessible name for the leave-voice-mode control.

**transcribingLabel** `string` (default: Transcribing…)

Fallback copy and accessible name while transcribing before
`transcript` arrives.

**transcript** `string`

Interim transcript while `voiceState` is `"transcribing"`.

**voiceState** `"idle" | "listening" | "transcribing"` (default: idle)

Voice-capture phase owned by the host.


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