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

# Agent Prompt - Web

> Agent prompts present an inline clarifying question for the user to answer before the agent continues.

> [!NOTE]
>
> For development, import `AgentPrompt` from
> `@servicetitan/anvil2-ai-kit`. For design, find it in the
> [AI Kit Web Component Library file](https://www.figma.com/design/QEIsArLktLeJAaFvcpw3PO/AI-Kit-Web-Component-Library).

ai-kit-agent-prompt

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

function App() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Question created by the agent, asking the user how to proceed."
        options={[
          { value: "a", label: "Choice A" },
          { value: "b", label: "Choice B" },
          { value: "c", label: "Choice C" },
        ]}
        value={value}
        onChange={setValue}
        allowOther={false}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

## Options

The Agent Prompt supports single-select and multi-select modes, a free-text option, and multi-step flows.

### Selection Modes

Each mode drives a different selection interaction. Single-select uses radio semantics; multi-select uses checkbox semantics.

#### Single Select

ai-kit-agent-prompt-single-select

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-single-select?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
  { value: "cookie", label: "Cookie Dough" },
];

function App() {
  const [value, setValue] = useState<string | null>(null);
  const [other, setOther] = useState("");

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Which flavor would you like?"
        options={options}
        value={value}
        onChange={setValue}
        otherValue={other}
        onOtherChange={setOther}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

Each option row acts as a radio — selecting one deselects all others. The selected row is outlined to confirm the choice.

#### Multi Select

ai-kit-agent-prompt-multi-select

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-multi-select?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
  { value: "cookie", label: "Cookie Dough" },
];

function App() {
  const [value, setValue] = useState<string[]>([]);
  const [other, setOther] = useState("");

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        selectionMode="multi"
        question="Pick any flavors you like"
        options={options}
        value={value}
        onChange={setValue}
        otherValue={other}
        onOtherChange={setOther}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

Each row carries a checkbox, and any number of options can be selected. The resolved answer is the set of checked values.

### Free Text

ai-kit-agent-prompt-free-text

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-free-text?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
];

function App() {
  const [value, setValue] = useState<string | null>(null);
  const [other, setOther] = useState("Pistachio");

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Which flavor would you like?"
        options={options}
        value={value}
        onChange={setValue}
        otherValue={other}
        onOtherChange={setOther}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

The "Something else…" free-text row renders when `onOtherChange` is wired. In single-select, the row is mutually exclusive with the option rows — typing deselects any chosen option, and selecting a row retains but subordinates the typed text. In multi-select, the typed value is an independent addition to the selection.

### Multi-step

ai-kit-agent-prompt-multi-step

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-multi-step?file=src/App.tsx)

```tsx lines theme={null}
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
];

const noop = () => {};

function App() {
  return (
    <div
      style={{
        display: "flex",
        gap: 24,
        alignItems: "flex-start",
        flexWrap: "wrap",
      }}
    >
      {/* First step — Next only */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which flavor?"
          options={options}
          value="choc"
          onChange={noop}
          allowOther={false}
          step={{ current: 1, total: 3 }}
          onNext={noop}
          onClose={noop}
        />
      </div>

      {/* Middle step — Back + Next */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which topping?"
          options={options}
          value="straw"
          onChange={noop}
          allowOther={false}
          step={{ current: 2, total: 3 }}
          onBack={noop}
          onNext={noop}
          onClose={noop}
        />
      </div>

      {/* Last step — Back + Send */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which size?"
          options={[
            { value: "sm", label: "Small" },
            { value: "md", label: "Medium" },
            { value: "lg", label: "Large" },
          ]}
          value="md"
          onChange={noop}
          allowOther={false}
          step={{ current: 3, total: 3 }}
          onBack={noop}
          onSend={noop}
          onClose={noop}
        />
      </div>
    </div>
  );
}

export default App;
```

Passing a `step` object switches the action row to a Back/Next/Send stepper and shows the current step count in the header. The first step shows only Next; middle steps show both Back and Next; the last step shows Back and Send.

## Behavior

The primary action is always enabled — attempting to send with nothing selected surfaces an inline error rather than disabling the button.

### Visual States

ai-kit-agent-prompt-visual-states

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-visual-states?file=src/App.tsx)

```tsx lines theme={null}
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
];

const noop = () => {};

function App() {
  return (
    <div
      style={{
        display: "flex",
        gap: 24,
        alignItems: "flex-start",
        flexWrap: "wrap",
      }}
    >
      {/* Empty — no selection */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which flavor?"
          options={options}
          value={null}
          onChange={noop}
          allowOther={false}
          onClose={noop}
        />
      </div>

      {/* Selected */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which flavor?"
          options={options}
          value="straw"
          onChange={noop}
          allowOther={false}
          onClose={noop}
        />
      </div>

      {/* Disabled */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which flavor?"
          options={options}
          value={null}
          onChange={noop}
          allowOther={false}
          onClose={noop}
          disabled
        />
      </div>
    </div>
  );
}

export default App;
```

The prompt renders in an empty (no selection), selected, and disabled state. The disabled state dims the options and action row but keeps the close button active so the user can always dismiss.

### Location

The Agent Prompt occupies the same position as the [Prompt Bar](https://anvil.servicetitan.com/docs/components/prompt-bar/design) — replacing it for the duration of the question. Once the user sends an answer, the Prompt Bar returns.

### Empty Answer Error

ai-kit-agent-prompt-empty-answer-error

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-empty-answer-error?file=src/App.tsx)

```tsx lines theme={null}
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "🍨 Vanilla" },
  { value: "straw", label: "🍓 Strawberry" },
];

function App() {
  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Which flavor would you like?"
        options={options}
        value={null}
        onChange={() => {}}
        allowOther={false}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

When the user presses Send or Next without selecting an option or entering free text, an error message appears beneath the options. Selecting any option or typing in the free-text field clears the error immediately.

## Usage Guidelines

### When to Use

Use the Agent Prompt when:

* **Clarifying questions** – The agent needs an answer from the user before it continues.
* **Structured answers** – The user answers by choosing one option or several options from a set.
* **Multi-part questions** – The agent needs answers to several questions in sequence. Use the multi-step flow to move through them with Back, Next, and Send.

### When not to use

Avoid using the Agent Prompt for:

* **Open-ended requests** – When the agent has no set of options to offer, let the user reply in the [Prompt Bar](https://anvil.servicetitan.com/docs/components/prompt-bar/web.md).
* **Optional follow-ups** – Use [Suggestions](https://anvil.servicetitan.com/docs/components/suggestion/web.md) to offer quick follow-up prompts beneath an agent response when the agent does not need an answer to continue.

## Keyboard Interaction

Users can navigate and operate the Agent Prompt using standard keyboard controls.

| Key | Description |
| - | - |
| Tab | Moves focus through the option group, free-text field, and buttons |
| Arrow Up / Down | Navigates between options in single-select mode |
| Space | Checks or unchecks the focused option in multi-select mode |
| Enter | Activates the focused button (Send, Next, Back, or Close) |

## Code

**Implementation**

ai-kit-agent-prompt

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

function App() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Question created by the agent, asking the user how to proceed."
        options={[
          { value: "a", label: "Choice A" },
          { value: "b", label: "Choice B" },
          { value: "c", label: "Choice C" },
        ]}
        value={value}
        onChange={setValue}
        allowOther={false}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

## Common Examples

To use the `AgentPrompt`, pass a `question`, an `options` array, controlled `value` / `onChange`, and a required `onClose` handler. The close button is always shown and remains available while the prompt is disabled.

```tsx theme={null}
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

function ExampleComponent() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <AgentPrompt
      question="How should I proceed?"
      options={[
        { value: "a", label: "Choice A" },
        { value: "b", label: "Choice B" },
      ]}
      value={value}
      onChange={setValue}
      onSend={(answer) => send(answer)}
      onClose={() => dismiss()}
    />
  );
}
```

### Selection mode

Single-select is the default. Each row is the control; the selected row is outlined. Set `selectionMode="multi"` for checkbox semantics and a `string[]` value.

ai-kit-agent-prompt-single-select

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-single-select?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
  { value: "cookie", label: "Cookie Dough" },
];

function App() {
  const [value, setValue] = useState<string | null>(null);
  const [other, setOther] = useState("");

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Which flavor would you like?"
        options={options}
        value={value}
        onChange={setValue}
        otherValue={other}
        onOtherChange={setOther}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

ai-kit-agent-prompt-multi-select

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-multi-select?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
  { value: "cookie", label: "Cookie Dough" },
];

function App() {
  const [value, setValue] = useState<string[]>([]);
  const [other, setOther] = useState("");

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        selectionMode="multi"
        question="Pick any flavors you like"
        options={options}
        value={value}
        onChange={setValue}
        otherValue={other}
        onOtherChange={setOther}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

### Free text

Wiring `onOtherChange` shows the "Something else…" row. In single-select, free text is mutually exclusive with a selected row; the text is retained if the user switches back. In multi-select, free text is independent and is appended to the selected values on send.

ai-kit-agent-prompt-free-text

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-free-text?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useState } from "react";
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
];

function App() {
  const [value, setValue] = useState<string | null>(null);
  const [other, setOther] = useState("Pistachio");

  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Which flavor would you like?"
        options={options}
        value={value}
        onChange={setValue}
        otherValue={other}
        onOtherChange={setOther}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

Pass `allowOther={false}` to hide the row even when a handler exists. Pass `true` without `onOtherChange` only in development as a misconfiguration.

### Multi-step

Pass `step={{ current, total }}` to replace the single **Send** with a stepper. Wire `onNext` to advance; without it, Next validates but does not move. `onBack` is never validated.

ai-kit-agent-prompt-multi-step

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-multi-step?file=src/App.tsx)

```tsx lines expandable theme={null}
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "Vanilla" },
  { value: "straw", label: "Strawberry" },
];

const noop = () => {};

function App() {
  return (
    <div
      style={{
        display: "flex",
        gap: 24,
        alignItems: "flex-start",
        flexWrap: "wrap",
      }}
    >
      {/* First step — Next only */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which flavor?"
          options={options}
          value="choc"
          onChange={noop}
          allowOther={false}
          step={{ current: 1, total: 3 }}
          onNext={noop}
          onClose={noop}
        />
      </div>

      {/* Middle step — Back + Next */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which topping?"
          options={options}
          value="straw"
          onChange={noop}
          allowOther={false}
          step={{ current: 2, total: 3 }}
          onBack={noop}
          onNext={noop}
          onClose={noop}
        />
      </div>

      {/* Last step — Back + Send */}
      <div style={{ width: 280 }}>
        <AgentPrompt
          question="Which size?"
          options={[
            { value: "sm", label: "Small" },
            { value: "md", label: "Medium" },
            { value: "lg", label: "Large" },
          ]}
          value="md"
          onChange={noop}
          allowOther={false}
          step={{ current: 3, total: 3 }}
          onBack={noop}
          onSend={noop}
          onClose={noop}
        />
      </div>
    </div>
  );
}

export default App;
```

### Empty answer

Send and Next stay enabled. Submitting with no selection and no free text shows the empty-answer error instead of calling `onSend` or `onNext`.

ai-kit-agent-prompt-empty-answer-error

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-agent-prompt-empty-answer-error?file=src/App.tsx)

```tsx lines expandable theme={null}
import { AgentPrompt } from "@servicetitan/anvil2-ai-kit";

const options = [
  { value: "choc", label: "Chocolate" },
  { value: "van", label: "🍨 Vanilla" },
  { value: "straw", label: "🍓 Strawberry" },
];

function App() {
  return (
    <div style={{ width: 380 }}>
      <AgentPrompt
        question="Which flavor would you like?"
        options={options}
        value={null}
        onChange={() => {}}
        allowOther={false}
        onClose={() => {}}
      />
    </div>
  );
}

export default App;
```

`onSend` and `onNext` receive a resolved answer: the selected option in single-select, or the checked values with non-empty free text appended in multi-select. Free-text strings pass through verbatim.

## Internationalization

`AgentPrompt` owns these localized strings:

* `ai-kit.agentPrompt.close`
* `ai-kit.agentPrompt.otherPlaceholder`
* `ai-kit.agentPrompt.send`
* `ai-kit.agentPrompt.back`
* `ai-kit.agentPrompt.next`
* `ai-kit.agentPrompt.emptyError`
* `ai-kit.agentPrompt.step`

Localize `question` and option labels before passing them. Override `otherPlaceholder` or `emptyErrorMessage` on the component, or mount `AiKitIntlProvider` inside `CartoTheme`. `step` receives `{current}` and `{total}`.

## React Accessibility

* The question labels the options group through `aria-labelledby`.
* Single-select uses radio semantics. Multi-select uses checkbox semantics.
* `headingLevel` sets the question's heading tag independently of visual size.
* Close remains available while `disabled` is true.

**AgentPrompt Props**

```tsx theme={null}
<AgentPrompt
  question="How should I proceed?"
  options={options}
  value={value}
  onChange={setValue}
  onSend={send}
  onClose={dismiss}
/>
```

## `AgentPrompt` Props

The `AgentPrompt` accepts the following props. It is a discriminated union on `selectionMode`. `value` and `onChange` match that mode.

**onChange** `(value: string | null) =\> void | (value: string[]) =\> void` *required*

Fired when the selection changes. Signature matches `selectionMode`.

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

Fired when the close button is pressed. Always required.

**options** `AgentPromptOption[]` *required*

Selectable options, rendered in order.

**question** `string` *required*

Question shown in the header and used as the options group's accessible
name.

**value** `string | null | string[]` *required*

Controlled selection. `string | null` in single-select; `string[]` in
multi-select.

**allowOther** `boolean`

Whether to show the free-text row. Defaults to whether `onOtherChange` is
provided.

**className** `string`

Class merged onto the root element.

**disabled** `boolean`

Disables the prompt. Close remains available.

**emptyErrorMessage** `string`

Override for the localized empty-answer error.

**headingLevel** `"h2" | "h3" | "h4" | "h5" | "h6"` (default: h2)

Semantic heading level for the question.

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

Fired when Back is pressed. Never validated.

**onNext** `(answer: string | null) =\> void | (answer: string[]) =\> void`

Fired when Next commits a valid answer. Required for the stepper to
advance.

**onOtherChange** `(value: string) =\> void`

Fired on every free-text edit. Wiring it shows the row by default.

**onSend** `(answer: string | null) =\> void | (answer: string[]) =\> void`

Fired when Send commits a valid answer. Receives the resolved answer.

**otherPlaceholder** `string`

Placeholder for the free-text row. Defaults to the localized "Something
else…".

**otherValue** `string`

Controlled free-text value.

**selectionMode** `"single" | "multi"` (default: single)

Selection semantics. Co-varies with `value` and `onChange`.

**step** `\{ current: number; total: number \}`

Multi-step position. Omit for a single Send action.

**AgentPromptOption**

```tsx theme={null}
const option: AgentPromptOption = {
  value: "choc",
  label: "Chocolate",
};
```

## `AgentPromptOption`

**label** `ReactNode` *required*

Display content for the option.

**value** `string` *required*

Stable identity used as the selection value and React key.

**isDisabled** `boolean`

Disables this option while leaving the rest interactive.


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