@servicetitan/carto-react-kit
v0.4.0
Published
Carto React component kit
Keywords
Readme
@servicetitan/carto-react-kit
Carto React component kit. Ships as ESM with type declarations.
Install
pnpm add @servicetitan/carto-react-kitPeer requirements: react@>=18 <20, react-dom@>=18 <20.
Usage
import { Button, CartoTheme } from "@servicetitan/carto-react-kit";
import { IconPencil } from "@servicetitan/carto-react-kit/icons";
import "@servicetitan/carto-react-kit/styles.css";
<CartoTheme>
<Button label="Save" onPress={handleSave} />
<Button label="Edit" icon={<IconPencil />} onPress={handleEdit} />
</CartoTheme>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 (Google Fonts) and Satoshi (Fontshare). For the fastest first paint, add the following <link> tags to your app shell <head> 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="preconnect" href="https://api.fontshare.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Inter:400,400i,500,500i,700,700i&display=optional"
/>
<link
rel="stylesheet"
href="https://api.fontshare.com/v2/css?f[]=satoshi@900,700,500,400&display=optional"
/>The kit also injects these links at module load as a runtime safety net, so components don't render in fallback indefinitely if you skip the shell setup — 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:carto-react-kit, pnpm test:carto-react-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/carto-react-kit exec playwright install chromiumYou 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:carto-react-kitStories render in light DOM by default. The Storybook toolbar has a Shadow toggle that wraps the story in an open shadow root (data-mfe-name="test-mfe"), mirroring how Carto components run inside MFE shadow roots in the host monolith. Switch to Shadow mode when you specifically want to validate CSS isolation against monolith styles — a red dotted outline flags the special mode.
Shadow mode clones document.head styles into the shadow root, surfacing collisions where monolith global selectors match Carto markup. It is not a fidelity environment for hover/focus testing — non-bubbling DOM events (pointerenter, mouseenter) can't cross shadow boundaries and React 18's polyfill can't bridge them across the outer-Storybook / inner-portal split. See src/internal/components/Shadow/Shadow.tsx for details; develop hover-driven components in Fragment mode.
Monolith CSS snapshot
preview-head.html loads real monolith stylesheets so the Storybook page environment matches what an MFE inherits from the host. A snapshot lives at public/monolith-css/. To refresh it:
pnpm refresh-monolith-css:carto-react-kitThe script sources the bundles two different ways:
- app-css, sui, kendo-css, main — pulled from the public ServiceTitan CDN (
https://go.servicetitan.com/scripts/build/). Hashes are resolved on the fly via thedesktopanddesktop-legacywebpack manifests, so refreshes pick up the current monolith build automatically. - anvil2, design-system — built locally from the
@servicetitan/anvil2and@servicetitan/design-systemnpm devDeps. anvil2 is concatenated fromdist/*.css(excluding bundles the package vendors from old monolith builds); design-system usesdist/system.cssdirectly. Therefresh-monolith-cssscript always runspnpm update @servicetitan/anvil2first so anvil2 is on the latest published version before the snapshot is rebuilt. design-system is frozen so it stays where it's pinned.
No monolith checkout is required.
