Gantry
Gantry scans your React, Vue or Svelte component library and design tokens, gives you a live visual browser in the sidebar, and keeps your AI assistant's rules up to date automatically — for both Cursor and Claude Code — so the AI always builds UI from your real design system instead of inventing markup.
WhyDesign systems drift. The AI in your editor doesn't know which components and tokens exist, so it reinvents buttons, hardcodes
Features
Quick start
Configuration —
|
| Field | Description |
|---|---|
componentsDir |
Where Gantry scans for components. |
tiers |
Folder names for the atomic preset (used when layers is omitted). When none of these folders exist, Gantry switches to flat-library mode. |
layers |
Optional — your own architecture layers (overrides tiers). |
rules.forbidRawElements |
Raw HTML elements the AI must not use directly (also drives drift detection). |
rules.forbidInlineStyles |
Forbid style={{}} / one-off classes in generated rules. |
tokens.sources |
Token sources to scan: css, dtcg, tailwind (default: all three; each is a no-op when nothing is found). |
tokens.dtcgFiles |
Explicit DTCG / Tokens Studio JSON files (relative). When empty, Gantry auto-discovers likely token JSON. |
tokens.tailwindConfig |
Explicit Tailwind config path (relative). When empty, auto-discovered. |
mcp.autoSetup |
Auto-register the MCP server in .cursor/mcp.json & .mcp.json on activation (default true). |
Tokens are scanned from your CSS custom properties (under :root in e.g. src/index.css), any DTCG / Tokens Studio JSON, and your Tailwind theme — merged into one catalog (CSS wins on name collisions, since it's the editable source).
Architecture layers
Gantry models your component architecture as an ordered list of layers (lowest → highest), each declaring which layers it may import from. The invariant Gantry enforces is: a component imports only from its layer's allowed layers, and the import graph stays acyclic. The classic atomic model (atom → molecule → organism) is just the default preset — zero-config projects behave exactly as before.
Define your own layers when atomic doesn't fit (e.g. primitives → components → features):
{
"componentsDir": "./src/components",
"layers": [
{ "name": "primitive", "folders": ["primitives"] },
{ "name": "component", "folders": ["components"], "canImport": ["primitive", "component"] },
{ "name": "feature", "folders": ["features"], "canImport": ["primitive", "component", "feature"] }
]
}
Per-layer fields: name, folders (folder names under componentsDir), canImport (allowed layer names — default: every strictly-lower layer, so omitting it gives classic layering), primitive (leaf that may render raw HTML — default: the lowest layer), and composes (must be built from catalog components, driving raw-element drift + coverage — default: !primitive).
Flat-library mode. If none of the configured layer folders exist (shadcn/Radix/MUI-style flat libraries), Gantry infers layers from the import graph instead: a component that imports no other catalog component is a primitive; one that composes others is a component. The only hard rule then is no circular dependencies.
CLI & CI
Gantry ships a gantry binary that runs the same engine headlessly — ideal for a CI gate that keeps the design system healthy across the whole team, not just in one editor.
# Fail the build if the design system regresses
npx gantry check --min-score 85 --max-errors 0
# Regenerate AI rules + catalog in a pre-commit hook or CI step
npx gantry sync
# Render a component variant to a PNG
npx gantry screenshot Button --variant secondary --out button.png
check exits non-zero when a threshold is crossed (--min-score, --max-errors, --max-warnings) and prints the offending drift with file:line; pass --json for machine-readable output. All commands accept --cwd <dir> to target another project root.
Example GitHub Actions step:
- run: npx gantry check --min-score 85 --max-errors 0
How it works
VS Code / Cursor
├── Extension host (Node)
│ ├── scanner reads components/ + props (ts-morph), CSS tokens, usage & coverage
│ ├── driftScanner detects layer/cycle/token/markup drift → Health
│ ├── generator writes .cursor/rules/gantry.mdc + .gantry/gantry-rules.md + CLAUDE.md
│ ├── catalog writes .gantry/catalog.json + registers the MCP config
│ ├── preview server Vite (or esbuild fallback) on a free port
│ └── webview provider wires the panel ↔ host
├── MCP server (separate Node process)
│ ├── serves the catalog/tokens/health to Cursor & Claude Code on demand
│ └── screenshot_component → drives a headless browser over the preview server → PNG
├── CLI (gantry) headless check / sync / screenshot for CI & scripts
└── Webview (React panel)
├── component tree + variant axes + properties
├── Health bar + token manager
└── <iframe> live preview (postMessage transport)
The preview writes a tiny app into __gantry_preview__/ and renders your component there using your project's real build pipeline.
Requirements
- VS Code
^1.85.0(or Cursor). - A TypeScript project in React, Vue or Svelte — scanned for the catalog, props, layers, drift, AI rules and MCP. Vite gives the best preview fidelity (Tailwind/PostCSS). Vue/Svelte live preview requires Vite (their SFCs compile through the project's
@vitejs/plugin-vue/@sveltejs/vite-plugin-svelte); the esbuild fallback is React-only (plain CSS / CSS Modules supported; Tailwind utilities are not processed in fallback mode). - Node.js 18+.
- Screenshots (
screenshot_component/gantry screenshot) need a Chromium-family browser already installed (Chrome, Edge, Brave, or Chromium) — Gantry drives it viapuppeteer-coreand does not download one. SetGANTRY_CHROMEto point at a specific executable if auto-detection misses it.
Development
git clone https://github.com/oleksiikoa/gantry.git
cd gantry
npm install
npm run build # builds the extension host (esbuild) + webview (vite)
# Press F5 in VS Code to launch the Extension Development Host
Useful scripts:
| Script | What it does |
|---|---|
npm run build |
Build dist/extension.js and dist/webview/. |
npm run watch |
Rebuild the extension host on change. |
npm test |
Run the Vitest suite (scanner, drift, rules generation, layers). |
npm run lint |
Lint the extension host, MCP server, CLI and tests. |
npm run package |
Produce a .vsix with vsce. |
The scanner is framework-agnostic and delegates component detection + prop/import
extraction to a FrameworkAdapter (src/core/frameworks/). React, Vue (.vue
SFCs — defineProps, withDefaults, runtime/array forms, Options API) and
Svelte (.svelte — export let props and Svelte 5 $props() runes) adapters
are implemented; set framework in gantry.config.json. The catalog, props,
layers, drift, AI rules, MCP and live preview work for all three — Vue/Svelte
render through the project's own Vite (the preview config merges your
vite.config, so your @vitejs/plugin-vue / @sveltejs/vite-plugin-svelte and
the installed runtime are reused). Without Vite, preview falls back to esbuild,
which is React-only.
License
PolyForm Strict 1.0.0 — free to use for any purpose, including commercial. You may not copy, distribute, or modify the software. Distribution or modification rights require a separate license.
