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

> The PromptBar provides a controlled composer for text, attachments, transcription, and sending.

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>;
  }
};

<Tabs>
  <Tab title="Implementation">
    <LiveCode showCode example="ai-kit-prompt-bar" fullWidth screenshot>
      ```tsx lines expandable theme={null}
      import { useState } from "react";
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

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

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

      export default App;
      ```
    </LiveCode>

    ## Common Examples

    To use the `PromptBar`, pass controlled `value` and `onChange`. Mode is derived from those inputs: idle when the draft is empty, draft when text or attachments are present, and transcribing when `transcription.active` is true.

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

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

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

    ### Draft and send

    Enter sends when sending is allowed. Sending is allowed when the draft has trimmed text or attachments, unless you override `canSend`. While `isSending` is true, sending is withheld. Wire `onStop` to surface Stop instead of Send.

    <LiveCode showCode example="ai-kit-prompt-bar-with-text" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { useState } from "react";
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

      function App() {
        const [value, setValue] = useState(
          "What's the weather like in San Francisco today?",
        );

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

      export default App;
      ```
    </LiveCode>

    <LiveCode showCode example="ai-kit-prompt-bar-draft-reflow" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { useState } from "react";
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

      function App() {
        const [value, setValue] = useState(
          "Can you help me draft a detailed message to the customer explaining the delay in their service appointment, the reason for the delay, and what we're doing to make it right? Please keep the tone apologetic but professional.",
        );

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

      export default App;
      ```
    </LiveCode>

    ### Attachments

    Pass `attachments` and wire `onAddAttachment` and `onRemoveAttachment`. Each attachment needs a stable `id` and `name`. `thumbnailUrl` fills the tile; without it, the tile shows the name.

    <LiveCode showCode example="ai-kit-prompt-bar-attachments" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { useState } from "react";
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

      function App() {
        const [value, setValue] = useState("");
        const [attachments, setAttachments] = useState([
          { id: "1", name: "invoice.pdf" },
          { id: "2", name: "site-photo.jpg" },
        ]);

        return (
          <PromptBar
            value={value}
            onChange={setValue}
            attachments={attachments}
            onAddAttachment={() => {}}
            onRemoveAttachment={(id) =>
              setAttachments((current) =>
                current.filter((attachment) => attachment.id !== id),
              )
            }
          />
        );
      }

      export default App;
      ```
    </LiveCode>

    ### Transcription

    Wire `onStartTranscription` for the mic control. Set `transcription.active` to enter transcribing mode. Prefer a real `MediaStream` for the waveform:

    ```tsx theme={null}
    <PromptBar
      value={value}
      onChange={setValue}
      onStartTranscription={startMic}
      transcription={{
        active: true,
        source: { type: "stream", stream },
      }}
      onCancelTranscription={cancel}
      onConfirmTranscription={confirm}
    />
    ```

    Set `transcription.error` to replace the waveform with an error message. Confirm then dismisses the error and exits transcription.

    <LiveCode showCode example="ai-kit-prompt-bar-transcribing" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { useState } from "react";
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

      function App() {
        const [value, setValue] = useState("");
        const [draftValue, setDraftValue] = useState(
          "What's the weather like in San Francisco today?",
        );

        return (
          <div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
            {/* Active transcription with a live waveform */}
            <PromptBar
              value={value}
              onChange={setValue}
              transcription={{
                active: true,
                source: { type: "preset", name: "speaking" },
              }}
              onCancelTranscription={() => {}}
              onConfirmTranscription={() => {}}
            />

            {/* Text in the prompt — no transcription active */}
            <PromptBar
              value={draftValue}
              onChange={setDraftValue}
              onSend={() => {}}
            />

            {/* Transcription error */}
            <PromptBar
              value={value}
              onChange={setValue}
              transcription={{ active: true, error: "Unable to access microphone" }}
              onCancelTranscription={() => {}}
              onConfirmTranscription={() => {}}
            />
          </div>
        );
      }

      export default App;
      ```
    </LiveCode>

    ### Disclaimer

    Omit `disclaimer` for the default Atlas notice. Pass `{ message, linkHref }` to customize it, wrapping the linked span in `<link>…</link>`. Pass `null` to hide the line.

    <LiveCode showCode example="ai-kit-prompt-bar-disclaimer" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { useState } from "react";
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

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

        return (
          <PromptBar
            value={value}
            onChange={setValue}
            disclaimer={{ linkHref: "https://example.com" }}
          />
        );
      }

      export default App;
      ```
    </LiveCode>

    ### Disabled

    `disabled` disables the whole bar.

    <LiveCode showCode example="ai-kit-prompt-bar-disabled" screenshot fullWidth>
      ```tsx lines expandable theme={null}
      import { PromptBar } from "@servicetitan/anvil2-ai-kit";

      function App() {
        return (
          <PromptBar
            value="What's the weather like in San Francisco today?"
            onChange={() => {}}
            disabled
          />
        );
      }

      export default App;
      ```
    </LiveCode>

    ## Internationalization

    `PromptBar` owns these localized strings:

    * `ai-kit.promptBar.placeholder`
    * `ai-kit.promptBar.add`
    * `ai-kit.promptBar.removeAttachment`
    * `ai-kit.promptBar.speak`
    * `ai-kit.promptBar.cancel`
    * `ai-kit.promptBar.send`
    * `ai-kit.promptBar.stop`
    * `ai-kit.promptBar.done`
    * `ai-kit.promptBar.dismiss`
    * `ai-kit.promptBar.disclaimer`
    * `ai-kit.promptBar.opensInNewTab`

    Override `placeholder` or `disclaimer.message` on the component, or mount `AiKitIntlProvider` inside `CartoTheme`. `removeAttachment` receives `{name}`. Disclaimer messages parse as ICU, so escape `{`, `}`, and `'` if they are literal.

    ## React Accessibility

    * Controls expose localized accessible names for add, remove, speak, send, stop, cancel, done, and dismiss.
    * Attachment remove labels include the attachment name.
    * Disclaimer links that open in a new tab include a localized announcement.
  </Tab>

  <Tab title="PromptBar Props">
    ```tsx theme={null}
    <PromptBar
      value={value}
      onChange={setValue}
      onSend={send}
      attachments={attachments}
      onAddAttachment={add}
      onRemoveAttachment={remove}
    />
    ```

    ## `PromptBar` Props

    The `PromptBar` accepts the following props.

    <ParamField path="onChange" type="(value: string) => void" required>
      Fired on every edit with the next draft value.
    </ParamField>

    <ParamField path="value" type="string" required>
      Current draft text. Controlled.
    </ParamField>

    <ParamField path="attachments" type="PromptAttachment[]">
      Attachments shown in the expanding attachment row.
    </ParamField>

    <ParamField path="canSend" type="boolean">
      Override for whether sending is allowed. Defaults to whether the draft
      has trimmed text or attachments.
    </ParamField>

    <ParamField path="className" type="string">
      Class merged onto the root element.
    </ParamField>

    <ParamField path="disabled" type="boolean">
      Disables the whole bar.
    </ParamField>

    <ParamField path="disclaimer" type="PromptBarDisclaimer | null">
      Disclaimer line. Omit for the default notice; pass `null` to hide it.
    </ParamField>

    <ParamField path="isSending" type="boolean">
      Whether a response is in flight. Withholds send and can surface Stop.
    </ParamField>

    <ParamField path="onAddAttachment" type="() => void">
      Fired when the add-attachment control is pressed.
    </ParamField>

    <ParamField path="onCancelTranscription" type="() => void">
      Fired when transcription cancel is pressed.
    </ParamField>

    <ParamField path="onConfirmTranscription" type="() => void">
      Fired when transcription confirm is pressed.
    </ParamField>

    <ParamField path="onRemoveAttachment" type="(id: string) => void">
      Fired when an attachment's remove control is pressed.
    </ParamField>

    <ParamField path="onSend" type="() => void">
      Fired when the send action fires from the button or Enter.
    </ParamField>

    <ParamField path="onStartTranscription" type="() => void">
      Fired when the transcribe control is pressed.
    </ParamField>

    <ParamField path="onStop" type="() => void">
      Fired when Stop is pressed. Surfaces only while `isSending` is true.
    </ParamField>

    <ParamField path="placeholder" type="string">
      Placeholder for the empty editor. Defaults to the localized "Ask
      anything…".
    </ParamField>

    <ParamField path="transcription" type="PromptBarTranscription">
      Live transcription feed. `active` selects transcribing mode.
    </ParamField>
  </Tab>

  <Tab title="PromptAttachment">
    ```tsx theme={null}
    const attachment: PromptAttachment = {
      id: "roof-photo",
      name: "roof.jpg",
      thumbnailUrl: "/previews/roof.jpg",
    };
    ```

    ## `PromptAttachment`

    <ParamField path="id" type="string" required>
      Stable identity used for removal and React keys.
    </ParamField>

    <ParamField path="name" type="string" required>
      Display name used for alt text, the remove label, and the fallback tile.
    </ParamField>

    <ParamField path="thumbnailUrl" type="string">
      Optional preview image. Omit to show the name in the tile.
    </ParamField>
  </Tab>
</Tabs>
