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

# Chat Layout

> Compose messages, rich content, actions, and prompts into an agent conversation.

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

The chat layout organizes the content and controls inside an agent conversation. It does not define the surrounding application shell.

## Complete chat layout

<LiveCode example="carto-patterns-chat-layout-complete-full-page" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState } from "react";
  import {
    ActionLog,
    AgentMediaCard,
    AgentMessage,
    AgentThinking,
    ArtifactCard,
    CartoTheme,
    ChatLayout,
    PromptBar,
    SuggestionList,
    Text,
    UserMessage,
    type ActionLogStep,
    type MediaAttachment,
  } from "@servicetitan/carto-react-kit";
  import * as cartoTokens from "@servicetitan/carto-react-kit/tokens";
  import "@servicetitan/carto-react-kit/styles.css";

  const { primitive } =
    cartoTokens as unknown as typeof import("@servicetitan/carto-tokens/vanilla-extract");

  const media: MediaAttachment = {
    kind: "document",
    fileName: "Service agreement.pdf",
    fileTypeLabel: "PDF",
    href: "https://example.com/service-agreement.pdf",
  };

  const actionSteps: ActionLogStep[] = [
    {
      id: "agreement",
      title: "Reviewed the service agreement",
      status: "success",
    },
    {
      id: "schedule",
      title: "Prepared the arrival window update",
      status: "success",
    },
  ];

  const timestamp = new Date(2026, 3, 1, 16, 40);

  function App() {
    const [draft, setDraft] = useState("");

    return (
      <CartoTheme>
        <div
          style={{
            position: "fixed",
            inset: 0,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            background: primitive.color.neutral["20"],
          }}
        >
          <div
            style={{
              width: 768,
              height: 1040,
              overflow: "hidden",
              background: primitive.color.neutral["0"],
              border: `1px solid ${primitive.color.neutral["80"]}`,
            }}
          >
            <ChatLayout
              conversation={
                <>
                  <UserMessage
                    content="Update the arrival window and summarize the agreement."
                    timestamp={timestamp}
                  />
                  <AgentMessage
                    content={
                      <>
                        <ActionLog steps={actionSteps} defaultExpanded={false} />
                        <Text text="I found the agreement and prepared the requested update." />
                        <AgentMediaCard media={media} showPreview={false} />
                        <ArtifactCard
                          artifactType="document"
                          title="Service agreement summary"
                          description="Key terms and the proposed schedule update."
                          onPress={() => {}}
                        />
                      </>
                    }
                    copyText="I found the agreement and prepared the requested update."
                    timestamp={new Date(2026, 3, 1, 16, 40, 30)}
                    onLike={() => {}}
                    onDislike={() => {}}
                  />
                  <AgentThinking />
                  <SuggestionList
                    suggestions={[
                      "Draft a customer update",
                      "Compare with the original",
                    ]}
                    onSelect={() => {}}
                  />
                </>
              }
              prompt={
                <PromptBar
                  value={draft}
                  onChange={setDraft}
                  onSend={() => setDraft("")}
                  placeholder="Ask anything…"
                />
              }
            />
          </div>
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

## User Message

<LiveCode example="carto-patterns-chat-layout-user-content" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    CartoTheme,
    SuggestionList,
    UserMediaMessage,
    UserMessage,
    type MediaAttachment,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const media: MediaAttachment = {
    kind: "document",
    fileName: "Service agreement.pdf",
    fileTypeLabel: "PDF",
    href: "https://example.com/service-agreement.pdf",
  };

  const timestamp = new Date(2026, 3, 1, 16, 40);

  function App() {
    return (
      <CartoTheme>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 16,
            width: "48rem",
          }}
        >
          <UserMediaMessage
            media={media}
            timestamp={timestamp}
            revealToolbarOnHover
          />
          <UserMessage
            content="Summarize this agreement and flag anything unusual."
            timestamp={timestamp}
          />
          <SuggestionList
            suggestions={["Focus on payment terms", "Check renewal conditions"]}
            onSelect={() => {}}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### User Message

<LiveCode example="carto-patterns-chat-layout-user-message" screenshot fullWidth>
  ```tsx lines theme={null}
  import { CartoTheme, UserMessage } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <UserMessage
            content="Summarize this agreement and flag anything unusual."
            timestamp={new Date(2026, 3, 1, 16, 40)}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

##### Multiple user messages

<LiveCode example="carto-patterns-chat-layout-user-message-multiple" screenshot fullWidth>
  ```tsx lines theme={null}
  import { CartoTheme, UserMessage } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 16,
            width: "48rem",
          }}
        >
          <UserMessage
            content="Summarize this agreement."
            timestamp={new Date(2026, 3, 1, 16, 38)}
            revealToolbarOnHover
          />
          <UserMessage
            content="Focus on payment terms and renewal conditions."
            timestamp={new Date(2026, 3, 1, 16, 39)}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### User Media

<LiveCode example="carto-patterns-chat-layout-user-media" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    CartoTheme,
    UserMediaMessage,
    type MediaAttachment,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const media: MediaAttachment[] = [
    {
      kind: "document",
      fileName: "Service agreement.pdf",
      fileTypeLabel: "PDF",
      href: "https://example.com/service-agreement.pdf",
    },
    {
      kind: "image",
      fileName: "Site photo.jpg",
      fileTypeLabel: "Image",
      src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='60'%3E%3Crect width='80' height='60' fill='%23dceeff'/%3E%3Cpath d='M8 48 28 26l14 14 10-10 20 18Z' fill='%2370b1ff'/%3E%3C/svg%3E",
      href: "https://example.com/site-photo.jpg",
    },
    {
      kind: "audio",
      fileName: "Customer call.mp3",
      fileTypeLabel: "Audio",
      src: "https://example.com/customer-call.mp3",
      href: "https://example.com/customer-call.mp3",
    },
    {
      kind: "video",
      fileName: "Walkthrough.mp4",
      fileTypeLabel: "Video",
      src: "https://example.com/walkthrough.mp4",
      href: "https://example.com/walkthrough.mp4",
    },
  ];

  function App() {
    return (
      <CartoTheme>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 16,
            width: "48rem",
          }}
        >
          {media.map((attachment, index) => (
            <UserMediaMessage
              key={attachment.fileName}
              media={attachment}
              timestamp={new Date(2026, 3, 1, 16, 37 + index)}
              showPreview={false}
              revealToolbarOnHover={index < media.length - 1}
            />
          ))}
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### Suggestions

<LiveCode example="carto-patterns-chat-layout-user-suggestions" screenshot fullWidth>
  ```tsx lines theme={null}
  import { CartoTheme, SuggestionList } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <SuggestionList
            suggestions={["Focus on payment terms", "Check renewal conditions"]}
            onSelect={() => {}}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### User Message Do's and Don'ts

| Guidance | Practice                                                                                  |
| -------- | ----------------------------------------------------------------------------------------- |
| Do       | Maintain the layout order of User Message content.                                        |
| Do       | Maintain consistent spacing between elements around the User Message.                     |
| Do       | On non-touch devices, reveal the toolbar on hover for messages before the latest message. |
| Don't    | Change the User Message bubble color.                                                     |
| Don't    | Change the User Message toolbar.                                                          |

## Agent Message

Agent message represents responses from the Agent. Unlike user messages, agent messages are fluid in their content and layout.

<LiveCode example="carto-patterns-chat-layout-agent-content" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    ActionLog,
    AgentMediaCard,
    AgentMessage,
    AgentThinking,
    ArtifactCard,
    CartoTheme,
    GuidanceCard,
    Markdown,
    type ActionLogStep,
    type GuidanceChangeSection,
    type MediaAttachment,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const steps: ActionLogStep[] = [
    {
      id: "review",
      title: "Reviewed the service agreement",
      status: "success",
    },
  ];

  const media: MediaAttachment = {
    kind: "document",
    fileName: "Service agreement.pdf",
    fileTypeLabel: "PDF",
    href: "https://example.com/service-agreement.pdf",
  };

  const changes: GuidanceChangeSection[] = [
    {
      type: "single",
      changes: [
        {
          id: "arrival-window",
          label: "Arrival window",
          from: "8–10 AM",
          to: "9–11 AM",
        },
      ],
    },
  ];

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <AgentMessage
            content={
              <>
                <ActionLog steps={steps} defaultExpanded={false} />
                <Markdown source="I reviewed the agreement and prepared a **summary** with the requested update." />
                <AgentMediaCard media={media} showPreview={false} />
                <ArtifactCard
                  artifactType="document"
                  title="Service agreement summary"
                  description="Key terms and the proposed schedule update."
                  onPress={() => {}}
                />
                <GuidanceCard
                  referenceLabel="Job #10428"
                  changeSections={changes}
                  onApply={() => {}}
                  onEdit={() => {}}
                  onDismiss={() => {}}
                />
              </>
            }
            copyText="I reviewed the agreement and prepared a summary."
            timestamp={new Date(2026, 3, 1, 16, 40)}
            onLike={() => {}}
            onDislike={() => {}}
          />
          <AgentThinking />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### Markdown Text

<LiveCode example="carto-patterns-chat-layout-agent-markdown" screenshot fullWidth>
  ```tsx lines theme={null}
  import { CartoTheme, Markdown } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <Markdown source="I reviewed the agreement and found **two items** that need attention." />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

##### Rich Markdown Agent Message

<LiveCode example="carto-patterns-chat-layout-agent-markdown-rich" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    AgentMessage,
    CartoTheme,
    Markdown,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const source = `### Agreement review

  I reviewed the **service agreement** and found *two items* that need attention. The source is available in the [customer record](https://example.com/customer-record).

  > The renewal window closes in 14 days.

  #### Review checklist

  - [x] Confirm payment terms
  - [x] Compare the current arrival window
  - [ ] Send the renewal notice

  #### Risk summary

  | Area | Status | Detail |
  | --- | --- | --- |
  | Payment | Clear | Net 30 terms |
  | Renewal | Review | Notice required |
  | Schedule | Updated | 9–11 AM |

  1. Confirm the proposed schedule.
  2. Notify the customer.
  3. Archive the previous version.

  The current setting is \`autoRenewal: true\`.

  \`\`\`json
  {
    "arrivalWindow": "9–11 AM",
    "noticeRequired": true
  }
  \`\`\`

  ~~No follow-up needed.~~ **Follow-up is required.**

  Visit https://example.com/agreements for the full policy.

  ---`;

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <AgentMessage
            content={<Markdown source={source} />}
            copyText={source}
            timestamp={new Date(2026, 3, 1, 16, 40)}
            onLike={() => {}}
            onDislike={() => {}}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

Example of using many markdown types in one Agent Message. For illustrative purposes of features, this should not be considered a high quality agent message.

Agent messages are primarily built on top of markdown text. The majority of markdown features are supported. See the [Markdown component page](/docs/kits/ai-kit/components/markdown/design) for each feature in action.

### Action Log

<LiveCode example="carto-patterns-chat-layout-agent-action-log" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    ActionLog,
    CartoTheme,
    type ActionLogStep,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const steps: ActionLogStep[] = [
    {
      id: "review",
      title: "Reviewed the service agreement",
      status: "success",
    },
    {
      id: "summary",
      title: "Prepared the summary",
      status: "success",
    },
  ];

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <ActionLog steps={steps} defaultExpanded={false} />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### Media Card

<LiveCode example="carto-patterns-chat-layout-agent-media" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    AgentMediaCard,
    CartoTheme,
    type MediaAttachment,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const media: MediaAttachment = {
    kind: "document",
    fileName: "Service agreement.pdf",
    fileTypeLabel: "PDF",
    href: "https://example.com/service-agreement.pdf",
  };

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <AgentMediaCard media={media} showPreview={false} />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

Media cards represent an asset within the chat layout.

Differences between media representation between agent and user messages:

* Agent media cards can be ordered anywhere in the agent message. User message media is fixed to above the message content.
* Agent media cards stretch to the width of the agent message.

### Artifact Card

<LiveCode example="carto-patterns-chat-layout-agent-artifact" screenshot fullWidth>
  ```tsx lines theme={null}
  import { ArtifactCard, CartoTheme } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <ArtifactCard
            artifactType="document"
            title="Service agreement summary"
            description="Key terms and the proposed schedule update."
            onPress={() => {}}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

Artifact cards represent an asset within the chat layout. When selected, content should display in the [artifact panel](/docs/kits/ai-kit/patterns/agentic-experience-shell#panel).

### Guidance Card

<LiveCode example="carto-patterns-chat-layout-agent-guidance" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    CartoTheme,
    GuidanceCard,
    type GuidanceChangeSection,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const changes: GuidanceChangeSection[] = [
    {
      type: "single",
      changes: [
        {
          id: "arrival-window",
          label: "Arrival window",
          from: "8–10 AM",
          to: "9–11 AM",
        },
      ],
    },
  ];

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <GuidanceCard
            referenceLabel="Job #10428"
            changeSections={changes}
            onApply={() => {}}
            onEdit={() => {}}
            onDismiss={() => {}}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### Toolbar

<LiveCode example="carto-patterns-chat-layout-agent-toolbar" screenshot fullWidth>
  ```tsx lines theme={null}
  import { AgentMessage, CartoTheme } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <AgentMessage
            content="I reviewed the agreement and prepared the requested update."
            timestamp={new Date(2026, 3, 1, 16, 40)}
            onLike={() => {}}
            onDislike={() => {}}
          />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

### Agent Thinking

<LiveCode example="carto-patterns-chat-layout-agent-thinking" screenshot fullWidth>
  ```tsx lines theme={null}
  import { AgentThinking, CartoTheme } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div style={{ width: "48rem" }}>
          <AgentThinking label="Atlas is thinking…" />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

Agent Thinking represents the visual representation of where the agent is currently thinking. There is exactly one Agent Thinking in the chat layout.

#### Examples of Agent Thinking location in action

<LiveCode example="carto-patterns-chat-layout-agent-thinking-after-user-message" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    AgentThinking,
    CartoTheme,
    UserMessage,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 8,
            width: "48rem",
          }}
        >
          <UserMessage
            content="User message"
            timestamp={new Date(2026, 3, 1, 16, 40)}
          />
          <AgentThinking label="Atlas is thinking…" />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

Just after a user message, Agent Thinking acts as a loading state.

<LiveCode example="carto-patterns-chat-layout-agent-thinking-streaming" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    AgentThinking,
    CartoTheme,
    Text,
    UserMessage,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  function App() {
    return (
      <CartoTheme>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 8,
            width: "48rem",
          }}
        >
          <UserMessage
            content="User message"
            timestamp={new Date(2026, 3, 1, 16, 40)}
          />
          <Text text="This is text the agent is actively streaming in right now. Additional content can continue to appear while Atlas works." />
          <AgentThinking />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

As text streams in, Agent Thinking sits below the incoming text. Its label is typically omitted during streaming.

<LiveCode example="carto-patterns-chat-layout-agent-thinking-finished-message" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    AgentMessage,
    AgentThinking,
    Card,
    CartoTheme,
    Text,
    UserMessage,
  } from "@servicetitan/carto-react-kit";
  import "@servicetitan/carto-react-kit/styles.css";

  const timestamp = new Date(2026, 3, 1, 16, 40);

  function App() {
    return (
      <CartoTheme>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 8,
            width: "48rem",
          }}
        >
          <UserMessage content="User message" timestamp={timestamp} />
          <AgentMessage
            content={
              <>
                <Text text="This is text the agent has finished streaming." />
                <Card
                  content={
                    <div
                      style={{
                        display: "flex",
                        justifyContent: "center",
                        padding: 32,
                      }}
                    >
                      <Text text="[Loaded Atlas content]" />
                    </div>
                  }
                />
                <Text text="More text has finished streaming. Atlas is done." />
              </>
            }
            copyText="This is text the agent has finished streaming. More text has finished streaming. Atlas is done."
            timestamp={timestamp}
            onLike={() => {}}
            onDislike={() => {}}
          />
          <AgentThinking />
        </div>
      </CartoTheme>
    );
  }

  export default App;
  ```
</LiveCode>

After an agent message finishes, Agent Thinking sits below the toolbar.

### Agent Message Do's and Don'ts

* Do: Maintain a consistent location of the Agent Toolbar. Outside of Agent Thinking, this is always at the bottom of the agent message.
* Do: Show the Agent Toolbar only after the agent message is finished.
* Do: Use loading mechanisms when possible to indicate the agent is processing the message.
* Do: Arrange the layout of Agent Message content to produce a clear and readable message. Unlike user messages, agent messages are fluid in order for most parts of the message.
* Don't: Use multiple Agent Thinkings in the chat layout.

## Related Components

* [Prompt Bar](/docs/kits/ai-kit/components/prompt-bar/design) — Accepts free-form user input
* [User Message](/docs/kits/ai-kit/components/user-message/design) — Presents user-authored content
* Agent Message — Presents agent-authored content and feedback actions
* Media — Presents user- or agent-authored attachments
* [Artifact Card](/docs/kits/ai-kit/components/artifact-card/design) — Opens generated content
* [Suggestions](/docs/kits/ai-kit/components/suggestion/design) — Offers concise follow-up actions
* [Agent Prompt](/docs/kits/ai-kit/components/agent-prompt/design) — Requests a structured answer
* [Guidance Card](/docs/kits/ai-kit/components/guidance-card/design) — Presents proposed changes for review

## Related Patterns

* [Agentic Experience Shell](/docs/kits/ai-kit/patterns/agentic-experience-shell) — Composes the chat layout with supporting content
