@servicetitan/anvil2-ai-kit
@servicetitan/anvil2-ai-kit
Anvil2 AI component kit. Ships as ESM with type declarations.
Install
pnpm add @servicetitan/anvil2-ai-kit
Peer requirements: react@>=18 <20, react-dom@>=18 <20.
Runtime dependency: @servicetitan/anvil2 (overlap primitives — Button, Text, Checkbox, Card, Dialog, Link, Menu — are not re-exported from this package).
Usage
import { Button } from "@servicetitan/anvil2";
import { AiKitTheme } from "@servicetitan/anvil2-ai-kit";
import { SvgPencil } from "@servicetitan/anvil2-ai-kit/icons";
import "@servicetitan/anvil2-ai-kit/styles.css";
import "@servicetitan/anvil2/dist/index.css"; // if your bundler needs an explicit CSS entry
<AiKitTheme>
{/* Overlap primitives (Button, Text, Card, …) come from @servicetitan/anvil2 */}
<Button appearance="primary" onClick={handleSave}>Save</Button>
<Button appearance="secondary" icon={{ before: SvgPencil }} onClick={handleEdit}>
Edit
</Button>
</AiKitTheme>
Always import the CSS bundle once at your app's entry point so vanilla-extract styles are loaded.
Icons ship as individual components under the ./icons subpath — IconPencil, IconChevronDown, etc. Each accepts the same locked prop surface (size, color, label, className, style, ref). The allowlist (Lucide-backed and custom Carto SVGs alike) is owned by VizD and curated inside the kit; consumers can't extend it from outside, and direct lucide-react imports outside this module are intentionally out of bounds.
Fonts
Carto components render in Inter (body) and Sofia Pro (headings). For the fastest first paint, add Inter <link> tags to your app shell <head> and ensure @servicetitan/anvil-fonts is loaded for Sofia Pro so the fetches start during HTML parse, before any Carto JS runs:
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Inter:400,400i,500,500i,700,700i&display=optional"
/>
import "@servicetitan/anvil-fonts/dist/css/anvil-fonts.css";
The kit also injects Inter links and imports anvil-fonts at module load as a runtime safety net — but the shell pattern is strictly preferable: fonts arrive before paint instead of after. See .storybook/preview-head.html for the canonical reference.
Source layout
Component code lives in src/components/<Component>/. Each component is a sibling group: <Component>.tsx (render only), <Component>.css.ts (vanilla-extract recipe), types.ts (props + variant unions), <Component>.test.tsx, and stories/<Component>.stories.tsx. Icons follow a slightly different shape — they ship from src/icons/ via factory functions; see src/icons/custom/README.md for the custom-icon workflow.
Common tasks (build, test, typecheck, typedoc, storybook) run from the repo root via pnpm build:anvil2-ai-kit, pnpm test:anvil2-ai-kit, etc. See the root package.json for the full list.
Local testing
Vitest is split into two projects (see vitest.config.ts):
unit— jsdom, runs every*.test.tsx. This is whatpnpm testand CI'sTestjob run.storybook— real Chromium via@vitest/browser-playwright, runs every*.stories.tsxthrough@storybook/addon-vitest. This is what the Run tests button in the Storybook UI andpnpm test-storybookuse locally. CI does not run this project — Chromatic catches play-function regressions in the cloud.
The storybook project needs Playwright's Chromium binary installed locally — it lives outside node_modules at ~/Library/Caches/ms-playwright/ (macOS) or ~/.cache/ms-playwright/ (Linux), ~150 MB. Run once per machine:
pnpm --filter @servicetitan/anvil2-ai-kit exec playwright install chromium
You only need this if you (a) click Run tests in the Storybook UI or (b) run pnpm test-storybook locally. Browsing stories with pnpm storybook and running unit tests with pnpm test do not need it.
Storybook
pnpm storybook:anvil2-ai-kit
Stories render in light DOM by default under AnvilProvider + AiKitTheme (light). Anvil2 component CSS and Core tokens come from the live @servicetitan/anvil2 package (lib-inject-css + AnvilProvider) — not from vendored monolith bundles.
The Storybook toolbar has a Shadow toggle that wraps the story in an open shadow root (data-mfe-name="test-mfe") for optional isolation checks. It is not a fidelity environment for hover/focus testing — non-bubbling DOM events (pointerenter, mouseenter) can't cross shadow boundaries. See src/internal/storybook/Shadow/Shadow.tsx; develop hover-driven components in Fragment mode.
Monolith CSS snapshot (not loaded in preview)
Outdated monolith host CSS is not linked from preview-head.html (it fights live Anvil2 after the hard-cut). A snapshot may still be refreshed into public/monolith-css/ for later clash-testing work:
pnpm refresh-monolith-css:anvil2-ai-kit
That script still downloads CDN bundles / builds local anvil2+design-system snapshots into public/monolith-css/, and regenerates the font-only preview-head.html. No monolith checkout is required.