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

# Agentic Experience Shell - Web

> Compose Carto components into a consistent shell for agentic experiences.

The agentic experience shell provides the persistent structure around a conversation. It defines navigation, presentation, and supporting workspaces without prescribing the content inside the conversation.

## Shell Options

The agent experience takes place in a shell with two shapes: the floating shell, and the fullscreen dialog shell experience.

### Floating shell

ai-kit-patterns-agentic-experience-shell-window-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-patterns-agentic-experience-shell-window-full-page?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import {
  AppHeader,
  CartoTheme,
  ChatHistory,
  ChatLayout,
  PromptBar,
  TabbedLayout,
  Text,
  Window,
  useAppSurface,
  type ChatHistoryItem,
  type TabbedLayoutTab,
} from "@servicetitan/anvil2-ai-kit";
import {
  IconGrip,
  IconInbox,
  IconMaximize2,
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import * as cartoTokens from "@servicetitan/anvil2-ai-kit/tokens";
import "@servicetitan/anvil2-ai-kit/styles.css";

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

const historyItems: ChatHistoryItem[] = [
  { id: "current", label: "Current conversation", subtitle: "Just now" },
  { id: "previous", label: "Previous conversation", subtitle: "Yesterday" },
];

const artifactTabs: TabbedLayoutTab[] = [
  {
    key: "preview",
    label: "Preview",
    content: <Text text="Generated artifact" />,
  },
];

function WindowHeader() {
  const {
    close,
    dragHandleProps,
    isFullscreen,
    toggleArtifact,
    toggleRail,
    toggleSize,
  } = useAppSurface();

  return (
    <AppHeader
      title="Title of conversation"
      leadingActions={
        isFullscreen
          ? [
              {
                id: "rail",
                icon: <IconPanelLeft />,
                label: "Toggle chat history",
                onPress: toggleRail,
              },
            ]
          : [
              {
                id: "drag",
                icon: <IconGrip />,
                label: "Move window",
                dragHandleProps,
              },
            ]
      }
      trailingActions={[
        ...(isFullscreen
          ? [
              {
                id: "artifact",
                icon: <IconPanelRight />,
                label: "Toggle artifact panel",
                onPress: toggleArtifact,
              },
            ]
          : []),
        {
          id: "inbox",
          icon: <IconInbox />,
          label: "Inbox",
          onPress: () => {},
        },
        {
          id: "size",
          icon: isFullscreen ? <IconMinimize2 /> : <IconMaximize2 />,
          label: isFullscreen ? "Restore" : "Maximize",
          onPress: toggleSize,
        },
        {
          id: "close",
          icon: <IconX />,
          label: "Close",
          onPress: close,
        },
      ]}
    />
  );
}

function App() {
  const [prompt, setPrompt] = useState("");

  return (
    <CartoTheme>
      <div
        style={{
          position: "fixed",
          inset: 0,
          overflow: "hidden",
          background: primitive.color.neutral["20"],
        }}
      >
        <Window
          header={<WindowHeader />}
          rail={
            <ChatHistory
              items={historyItems}
              defaultSelectedKeys={["current"]}
            />
          }
          chat={
            <ChatLayout
              conversation={
                <div style={{ padding: 16 }}>
                  <Text text="Conversation content" />
                </div>
              }
              prompt={
                <PromptBar
                  value={prompt}
                  onChange={setPrompt}
                  placeholder="Ask anything…"
                />
              }
            />
          }
          artifact={<TabbedLayout tabs={artifactTabs} />}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

### Fullscreen shell

ai-kit-patterns-agentic-experience-shell-fullscreen-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-patterns-agentic-experience-shell-fullscreen-full-page?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import {
  AppHeader,
  CartoTheme,
  ChatHistory,
  ChatLayout,
  PromptBar,
  TabbedLayout,
  Text,
  Window,
  useAppSurface,
  type ChatHistoryItem,
  type TabbedLayoutTab,
} from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

const historyItems: ChatHistoryItem[] = [
  { id: "current", label: "Current conversation", subtitle: "Just now" },
  { id: "previous", label: "Previous conversation", subtitle: "Yesterday" },
];

const artifactTabs: TabbedLayoutTab[] = [
  {
    key: "preview",
    label: "Preview",
    content: <Text text="Generated artifact" />,
  },
];

function WindowHeader() {
  const { close, toggleArtifact, toggleRail, toggleSize } = useAppSurface();

  return (
    <AppHeader
      title="Title of conversation"
      leadingActions={[
        {
          id: "history",
          icon: <IconPanelLeft />,
          label: "Toggle chat history",
          onPress: toggleRail,
        },
      ]}
      trailingActions={[
        {
          id: "artifact",
          icon: <IconPanelRight />,
          label: "Toggle artifact panel",
          onPress: toggleArtifact,
        },
        {
          id: "restore",
          icon: <IconMinimize2 />,
          label: "Restore",
          onPress: toggleSize,
        },
        {
          id: "close",
          icon: <IconX />,
          label: "Close",
          onPress: close,
        },
      ]}
    />
  );
}

function App() {
  const [prompt, setPrompt] = useState("");

  return (
    <CartoTheme>
      <div style={{ position: "fixed", inset: 0, background: "#e9ecf1" }}>
        <Window
          defaultState="fullscreen"
          header={<WindowHeader />}
          rail={
            <ChatHistory
              items={historyItems}
              defaultSelectedKeys={["current"]}
            />
          }
          chat={
            <ChatLayout
              prompt={
                <PromptBar
                  value={prompt}
                  onChange={setPrompt}
                  placeholder="Ask anything…"
                />
              }
            />
          }
          artifact={<TabbedLayout tabs={artifactTabs} />}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

Both shells are available to a user. Implementors can default which experience a user lands on, but cannot prevent the user from switching to the other.

#### Feature Options

| Capability | Window Shell | Fullscreen Shell |
| - | - | - |
| Artifact panel | Unavailable | Available |
| Chat history | Available | Available |
| New chat | Available | Available |

#### When to default to which shell?

| Presentation | Use when |
| - | - |
| Window Shell | Context from the underlying page remains important |
| Fullscreen Shell | The workflow requires dedicated focus or an artifact |

## Supporting content

Supporting content can appear in the App Shell’s artifact panel. The content inside that panel can use a Tabbed Layout when users need access to multiple related views.

### Panel

ai-kit-patterns-agentic-experience-shell-panel-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-patterns-agentic-experience-shell-panel-full-page?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import {
  AppHeader,
  CartoTheme,
  ChatHistory,
  ChatLayout,
  PromptBar,
  Text,
  Window,
  useAppSurface,
  type ChatHistoryItem,
} from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

const historyItems: ChatHistoryItem[] = [
  { id: "current", label: "Current conversation", subtitle: "Just now" },
  { id: "previous", label: "Previous conversation", subtitle: "Yesterday" },
];

function WindowHeader() {
  const { close, toggleArtifact, toggleRail, toggleSize } = useAppSurface();

  return (
    <AppHeader
      title="Title of conversation"
      leadingActions={[
        {
          id: "history",
          icon: <IconPanelLeft />,
          label: "Toggle chat history",
          onPress: toggleRail,
        },
      ]}
      trailingActions={[
        {
          id: "artifact",
          icon: <IconPanelRight />,
          label: "Toggle artifact panel",
          onPress: toggleArtifact,
        },
        {
          id: "restore",
          icon: <IconMinimize2 />,
          label: "Restore",
          onPress: toggleSize,
        },
        {
          id: "close",
          icon: <IconX />,
          label: "Close",
          onPress: close,
        },
      ]}
    />
  );
}

function App() {
  const [prompt, setPrompt] = useState("");

  return (
    <CartoTheme>
      <Window
        defaultState="fullscreen"
        header={<WindowHeader />}
        rail={
          <ChatHistory items={historyItems} defaultSelectedKeys={["current"]} />
        }
        chat={
          <ChatLayout
            prompt={
              <PromptBar
                value={prompt}
                onChange={setPrompt}
                placeholder="Ask anything…"
              />
            }
          />
        }
        artifact={
          <div style={{ height: "100%", padding: 16 }}>
            <Text text="Supporting content" />
          </div>
        }
      />
    </CartoTheme>
  );
}

export default App;
```

### Tabbed layout

ai-kit-patterns-agentic-experience-shell-tabbed-layout-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-patterns-agentic-experience-shell-tabbed-layout-full-page?file=src/App.tsx)

```tsx lines theme={null}
import { useState } from "react";
import {
  AppHeader,
  CartoTheme,
  ChatHistory,
  ChatLayout,
  PromptBar,
  TabbedLayout,
  Text,
  Window,
  useAppSurface,
  type ChatHistoryItem,
  type TabbedLayoutTab,
} from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

const historyItems: ChatHistoryItem[] = [
  { id: "current", label: "Current conversation", subtitle: "Just now" },
  { id: "previous", label: "Previous conversation", subtitle: "Yesterday" },
];

const artifactTabs: TabbedLayoutTab[] = [
  {
    key: "preview",
    label: "Preview",
    content: <Text text="Generated artifact" />,
  },
  {
    key: "details",
    label: "Details",
    content: <Text text="Artifact details" />,
  },
];

function WindowHeader() {
  const { close, toggleArtifact, toggleRail, toggleSize } = useAppSurface();

  return (
    <AppHeader
      title="Title of conversation"
      leadingActions={[
        {
          id: "history",
          icon: <IconPanelLeft />,
          label: "Toggle chat history",
          onPress: toggleRail,
        },
      ]}
      trailingActions={[
        {
          id: "artifact",
          icon: <IconPanelRight />,
          label: "Toggle artifact panel",
          onPress: toggleArtifact,
        },
        {
          id: "restore",
          icon: <IconMinimize2 />,
          label: "Restore",
          onPress: toggleSize,
        },
        {
          id: "close",
          icon: <IconX />,
          label: "Close",
          onPress: close,
        },
      ]}
    />
  );
}

function App() {
  const [prompt, setPrompt] = useState("");

  return (
    <CartoTheme>
      <Window
        defaultState="fullscreen"
        header={<WindowHeader />}
        rail={
          <ChatHistory items={historyItems} defaultSelectedKeys={["current"]} />
        }
        chat={
          <ChatLayout
            prompt={
              <PromptBar
                value={prompt}
                onChange={setPrompt}
                placeholder="Ask anything…"
              />
            }
          />
        }
        artifact={<TabbedLayout tabs={artifactTabs} />}
      />
    </CartoTheme>
  );
}

export default App;
```

## Dos and Don'ts for Agentic Experience Shell

| Guidance | Practice |
| - | - |
| Do | Use an artifact panel when a workflow benefits from dedicated space to view and interact with related content. |
| Don't | Place an artifact panel in the floating shell. |
| Don't | Modify the left rail with content other than chat history. |
| Don't | Change the floating shell's default location. |

## Related components

* [App Header](https://anvil.servicetitan.com/docs/components/app-header/design) — Identifies the experience and provides shell-level actions
* [App Shell](https://anvil.servicetitan.com/docs/components/app-shell/design) — Arranges the header, history rail, conversation, and supporting panel
* [Chat History](https://anvil.servicetitan.com/docs/components/chat-history/design) — Provides access to previous conversations
* [Chat Layout](https://anvil.servicetitan.com/docs/components/chat-layout/design) — Defines the conversation region
* [Tabbed Layout](https://anvil.servicetitan.com/docs/components/tabbed-layout/design) — Organizes multiple related views within the supporting workspace
* [Window](https://anvil.servicetitan.com/docs/components/window/design) — Presents the shell as a floating or fullscreen experience

## Related patterns

* [Atlas Entry Points](https://anvil.servicetitan.com/docs/patterns/atlas-entry-points/web.md) — Where a user begins or resumes an Atlas conversation.
* [Chat Layout](https://anvil.servicetitan.com/docs/patterns/ai-chat-layout/web.md) — For the layout of chat messages within the shell.


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