Skip to main content

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.

Common Examples

ArtifactPanel reads its responsive mode (inline vs. overlay) from the surrounding ArtifactPanelLayout. Wrap chat content and any artifact panels in ArtifactPanelLayout so the panel can switch modes when the available width changes. Outside a layout the panel falls back to inline mode.

Basic Usage

Wrap chat content and the panel in ArtifactPanelLayout. Open and close the panel from consumer state:
import { useState } from "react";
import {
  ArtifactPanel,
  ArtifactPanelLayout,
} from "@servicetitan/anvil2-ext-atlas";

function ArtifactDetails({ children }) {
  const [open, setOpen] = useState(false);
  return (
    <ArtifactPanelLayout>
      {children}
      <ArtifactPanel
        isOpen={open}
        onOpenChange={setOpen}
        title="Supplemental info"
      >
        {/* artifact content */}
      </ArtifactPanel>
    </ArtifactPanelLayout>
  );
}

Swapping Artifacts While Open

Pass triggerKey so the panel treats a value change as a new open event — focus shifts into the panel for the new artifact, and close-restore returns focus to the most recent trigger:
import { useState } from "react";
import { ArtifactCard, ArtifactPanel } from "@servicetitan/anvil2-ext-atlas";

function ChatWithArtifacts() {
  const [activeId, setActiveId] = useState<string | null>(null);
  return (
    <>
      <ArtifactCard
        title="Insight 1"
        description="..."
        artifactId="insight-1"
        active={activeId === "insight-1"}
        onClick={() => setActiveId("insight-1")}
      />
      <ArtifactCard
        title="Insight 2"
        description="..."
        artifactId="insight-2"
        active={activeId === "insight-2"}
        onClick={() => setActiveId("insight-2")}
      />
      <ArtifactPanel
        isOpen={activeId !== null}
        onOpenChange={(open) => {
          if (!open) setActiveId(null);
        }}
        title={`Insight ${activeId ?? ""}`}
        triggerKey={activeId ?? undefined}
      >
        {/* artifact content */}
      </ArtifactPanel>
    </>
  );
}

Agent-Initiated Open

For panels opened by the agent rather than a user gesture, set focusOnOpen to false so focus is not pulled away from the chat composer:
<ArtifactPanel
  isOpen={agentOpened}
  onOpenChange={setAgentOpened}
  title="Atlas opened this for you"
  focusOnOpen={false}
>
  {/* artifact content */}
</ArtifactPanel>

React Accessibility

  • The panel renders an <aside> landmark with aria-labelledby tied to the title and aria-describedby tied to the description when present.
  • Focus moves into the panel on open (default behavior) and returns to the previously focused element on close.
  • Slide and fade animations respect prefers-reduced-motion.
  • The panel is not modal — chat content remains interactive while the panel is open. The panel does not handle Escape or click-outside; those belong to the parent chat.
  • When ArtifactPanelLayout switches the panel from inline to overlay mode at narrow widths, the chat content beneath remains in the document flow and reachable by assistive technology.
Last modified on May 8, 2026