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

# GuidanceCard – Design

> Guidance cards surface recommendations or instructional content.

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

export const CodePreviewPlaceholder = ({double, fullWidth}) => {
  const single = <div style={{
    width: fullWidth ? "100%" : "50%",
    borderRadius: "1rem",
    display: "flex",
    padding: "1rem",
    flexDirection: "column",
    gap: "0.5rem",
    height: "10rem",
    marginBlockEnd: "1rem"
  }} className="border-width-default border-color-subdued">
      <div className="bg-strong border-radius-large" style={{
    width: "100%",
    flexGrow: "1"
  }} />
      <div className="bg-strong border-radius-large" style={{
    width: "100%",
    flexGrow: "1"
  }} />
    </div>;
  return double ? <div style={{
    display: "flex",
    gap: "1rem"
  }}>
      {single}
      {single}
    </div> : single;
};

<LiveCode example="carto-guidance-card" screenshot fullWidth>
  ```tsx lines theme={null}
  import { GuidanceCard } from "@servicetitan/carto-react-kit";

  function App() {
    return (
      <div style={{ width: 530 }}>
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={[
            {
              type: "single",
              changes: [
                {
                  id: "labor-hours",
                  label: "Labor hours",
                  from: "6.5h",
                  to: "8.0h",
                },
              ],
            },
          ]}
        />
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

## Options

The Guidance Card supports four lifecycle states, single and table change sections, and an optional linked reference.

### States

<LiveCode example="carto-guidance-card-states" screenshot fullWidth>
  ```tsx lines theme={null}
  import { GuidanceCard } from "@servicetitan/carto-react-kit";

  const sections = [
    {
      type: "single" as const,
      changes: [
        { id: "labor-hours", label: "Labor hours", from: "6.5h", to: "8.0h" },
      ],
    },
  ];

  function App() {
    return (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 16,
          width: 530,
        }}
      >
        {/* Default — interactive review */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={sections}
        />

        {/* Accepted — collapsed summary */}
        <GuidanceCard
          state="accepted"
          referenceLabel="Invoice #1234"
          changeSections={sections}
          appliedChangeIds={["labor-hours"]}
        />

        {/* Rejected — collapsed summary */}
        <GuidanceCard
          state="rejected"
          referenceLabel="Invoice #1234"
          changeSections={sections}
        />

        {/* Skipped — collapsed summary */}
        <GuidanceCard
          state="skipped"
          referenceLabel="Invoice #1234"
          changeSections={sections}
        />
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

`default` shows selectable diffs with Accept, Edit, and Reject actions. `accepted`, `rejected`, and `skipped` collapse the same content into a read-only disclosure that starts closed — expand it to review what was suggested.

### Change Sections

<LiveCode example="carto-guidance-card-change-sections" screenshot fullWidth>
  ```tsx lines theme={null}
  import { GuidanceCard } from "@servicetitan/carto-react-kit";

  function App() {
    return (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 24,
          width: 530,
        }}
      >
        {/* Single section — scalar diff rows */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={[
            {
              type: "single",
              changes: [
                {
                  id: "labor-hours",
                  label: "Labor hours",
                  from: "6.5h",
                  to: "8.0h",
                },
                {
                  id: "labor-rate",
                  label: "Labor rate",
                  from: "$95/hr",
                  to: "$110/hr",
                },
              ],
            },
          ]}
        />

        {/* Table section — labeled line-item table */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={[
            {
              type: "table",
              label: "Line items",
              changeTables: [
                {
                  label: "New",
                  columns: [
                    { key: "name", header: "Name", minWidth: 160 },
                    {
                      key: "description",
                      header: "Description",
                      minWidth: 220,
                    },
                  ],
                  data: [
                    {
                      id: "condensate-drain",
                      name: { type: "addition", value: "Condensate Drain" },
                      description: {
                        type: "addition",
                        value: "Quarterly Maintenance",
                      },
                    },
                  ],
                },
              ],
            },
          ]}
        />
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

A change section is either `single` — one or more scalar diff rows — or `table` — one or more labeled line-item tables. Combine both types, in any order, within `changeSections` to build a mixed review.

### Reference Link

<LiveCode example="carto-guidance-card-reference-link" screenshot fullWidth>
  ```tsx lines theme={null}
  import { GuidanceCard } from "@servicetitan/carto-react-kit";

  const sections = [
    {
      type: "single" as const,
      changes: [
        { id: "labor-hours", label: "Labor hours", from: "6.5h", to: "8.0h" },
      ],
    },
  ];

  function App() {
    return (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 16,
          width: 530,
        }}
      >
        {/* Plain text reference */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={sections}
        />

        {/* Linked reference — opens in a new tab */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          referenceHref="https://example.com/estimates/12345"
          changeSections={sections}
        />
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

`referenceHref` turns the reference label into a link that opens the related estimate or invoice in a new tab. Omit it to render the label as plain text.

## Behavior

In the default state, every change starts selected; the card tracks its own selection from there and disables its footer actions once everything is deselected.

### Selection

<LiveCode example="carto-guidance-card-selection" screenshot fullWidth>
  ```tsx lines theme={null}
  import { GuidanceCard } from "@servicetitan/carto-react-kit";

  function App() {
    return (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 24,
          width: 530,
        }}
      >
        {/* Multiple changes — each row gets its own checkbox */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={[
            {
              type: "single",
              changes: [
                {
                  id: "labor-hours",
                  label: "Labor hours",
                  from: "6.5h",
                  to: "8.0h",
                },
                {
                  id: "labor-rate",
                  label: "Labor rate",
                  from: "$95/hr",
                  to: "$110/hr",
                },
              ],
            },
          ]}
        />

        {/* One change — no checkbox; Accept applies to the sole item */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={[
            {
              type: "single",
              changes: [
                {
                  id: "labor-hours",
                  label: "Labor hours",
                  from: "6.5h",
                  to: "8.0h",
                },
              ],
            },
          ]}
        />
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

When a section has more than one selectable change, each row starts checked and gets its own checkbox — uncheck a row to exclude it before accepting. A lone change omits the checkbox entirely; Accept always applies to it.

### Actions

<LiveCode example="carto-guidance-card-actions" screenshot fullWidth>
  ```tsx lines theme={null}
  import { GuidanceCard } from "@servicetitan/carto-react-kit";

  const changes = [
    { id: "labor-hours", label: "Labor hours", from: "6.5h", to: "8.0h" },
    { id: "labor-rate", label: "Labor rate", from: "$95/hr", to: "$110/hr" },
  ];
  const sections = [{ type: "single" as const, changes }];

  function App() {
    return (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 24,
          width: 530,
        }}
      >
        {/* Default — every change starts selected; Accept and Edit are enabled */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={sections}
        />

        {/* Everything deselected — Accept and Edit become disabled */}
        <GuidanceCard
          referenceLabel="Estimate #12345"
          changeSections={sections}
        />
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

Accept and Edit are enabled by default — every change starts selected — and disable only once every change is deselected. Reject is always available and needs no selection. Accept and Edit each receive the ids of the currently selected changes and table rows.

## Keyboard Interaction

Users can navigate, select, and act on the Guidance Card using standard keyboard controls.

| Key           | Description                                                          |
| ------------- | -------------------------------------------------------------------- |
| Tab           | Moves focus among checkboxes, the reference link, and footer actions |
| Space         | Toggles the focused checkbox                                         |
| Enter / Space | Activates the focused reference link or footer action                |
