> ## 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 – Design

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

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

<LiveCode example="carto-agent-prompt" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState } from "react";
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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

<LiveCode example="carto-agent-prompt-single-select" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState } from "react";
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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

#### Multi Select

<LiveCode example="carto-agent-prompt-multi-select" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState } from "react";
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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

### Free Text

<LiveCode example="carto-agent-prompt-free-text" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState } from "react";
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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

<LiveCode example="carto-agent-prompt-multi-step" screenshot fullWidth>
  ```tsx lines theme={null}
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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

<LiveCode example="carto-agent-prompt-visual-states" screenshot fullWidth>
  ```tsx lines theme={null}
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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](/docs/kits/ai-kit/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

<LiveCode example="carto-agent-prompt-empty-answer-error" screenshot fullWidth>
  ```tsx lines theme={null}
  import { AgentPrompt } from "@servicetitan/carto-react-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;
  ```
</LiveCode>

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.

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