Getting started

Import your design system

Statecraft renders your real components on a real canvas — so the first step is pointing it at the library your team already ships. Once it's in, every prototype designers make uses the same buttons, cards, and tokens your users see in production. No re-mocking, no drift.

The standard route: import from a repo

If your library lives on GitHub, the fastest path is Import from repo on the workspace's Design systems page. One-time setup: install the Statecraft GitHub App on the account or org that owns the repo (Account → GitHub access → Install GitHub App). After that:

  1. Click Import from repo and pick the repo from the dropdown.
  2. Statecraft reads it and proposes a config — framework (React or Vue), what to use as the components entry point, which Tailwind / CSS-in-JS pipeline is in play, which components to expose, and the styling context your components need (a theme provider, a global stylesheet, etc.) so they don't render unstyled.
  3. Review the proposal. Refine in plain English — "exclude the Skeleton component", "install lives in packages/ui", "add a dark-theme variant" — and Statecraft re-verifies the build.
  4. When it looks right, click Publish. The design system is immediately available to everyone in the workspace — it shows up in the in-editor Add a design system picker that opens with each new project.
design-systems-page-new-button.png

Most teams are done in under five minutes. The result works the same as any other design system in the workspace — designers create projects against it, and the components they place are the real ones from your repo.

Don't have a component library yet?

Three lightweight starting points if you're not pointing at an existing repo:

  • Fork a gallery starter. Install one of the Core gallery entries (Ant Design, Material UI, Radix, shadcn/ui, Chakra, Plain HTML) from Browse gallery. Use it directly, or open the row, copy the YAML, click New design system, paste it in, rename, and edit from there.
  • Start from a stylesheet. If your design system is a CSS framework (Bulma, Bootstrap, BEM kit, your own), pick Start from CSS in the New design system dialog. Paste the stylesheet URL, list component names, and Statecraft synthesises tiny wrapper components for you. The lightest possible onboarding path — no bundle to host, no repo to point at.
  • Plain HTML. Already in every workspace. Exposes div, button, input, form, and other bare elements as the available tags. Good for early sketches before you have any library at all.

All three work for React and Vue projects — pick the framework on the New design system form.

From an npm package

For libraries published to npm (Chakra UI, Mantine, Headless UI, etc.), New design system → npm package lets you point at a package name and an exact version. Statecraft resolves it via esm.sh at render time — no build step on your side, no bundle to host. Three taps to a working design system for any ESM-compatible React component library.

npm-package-starter.png

After connecting

A newly-added design system shows up immediately in the in-editor Add a design system picker that opens with each new project. Existing projects keep the design system they were created with; there's no in-UI picker to swap the design system on an existing project (a project picks a framework + DS at creation and locks both for life).

Next up: map a journey from your codebase onto the canvas, or prototype how you like.

What you're configuring — the three pointers

Every design system, however it's imported, boils down to three pointers into your codebase. The browser importer infers them for you; the desktop tray and CLI take them as flags; the published YAML carries them as fields:

  • Styling — your globals.css, tailwind.config.*, design-token JSON, or whatever carries your colours, spacing, and theme variables.
  • Components — the directory or package entry that exports the components you want available on the canvas (React components for React projects, Vue components for Vue projects).
  • Frame — optional but often what separates "styled" from "unstyled". A wrapper component that mirrors your real <App>'s provider stack (<ThemeProvider>, query client, route mocks, etc.) and pulls in any global stylesheet your entry doesn't import, so components render in the same context they would in production. Point it at an existing module, or — in the browser importer — let Statecraft author one for you when the context is scattered across your app shell.

Continuous publishing from your machine

The browser importer publishes once per refinement round. If you're iterating on a component library locally and want every save to bounce to the canvas in ~2 seconds — without re-importing — install the desktop tray app. The tray runs a daemon that watches your repo, rebundles on every save, and uploads to Statecraft.

Open the wizard from any of three places: the menubar dropdown (Add a design system…), the Design systems panel in Settings, or the first-run onboarding banner. The wizard:

desktop-app-settings-import.png
  1. Pick the repo root, the workspace, a slug (lower-case kebab, 3–32 chars — used as the import name in canvas JSX), and an optional display name.
  2. Pick the main file that exports your components (usually src/index.ts). Four shortcut chips (src/index.ts, src/index.tsx, src/index.js, index.ts) pre-fill the path so you can confirm with a click. Optionally expand Add a wrapper component for a default-export wrapper that provides theme, query-client, and route mocks.
  3. Click Add design system. The daemon writes a statecraft.yaml manifest in your repo (the single source of truth for the build — entry, framework, install, Tailwind, etc.), records a thin pointer to it in .statecraft/live.json, registers the design system on the server with kind: live, and starts watching. The first build can take a minute on first install. The Design systems panel shows live status (Building / Live / Failed) for every registered DS. Edit statecraft.yaml and save anytime to change build config — the daemon rebuilds.

Once the first bundle is uploaded, your machine isn't load-bearing — collaborators keep rendering the last-published bundle even with your daemon offline. Build failures (TypeScript errors, missing imports) surface in the Design systems panel as a red row with a classified hint plus a prompt to edit statecraft.yaml and save. The bundle pointer is intentionally untouched on failure, so teammates keep rendering the last-good bundle while you fix the error.

From the CLI

The same registration is statecraft import --repo-root . --framework react --slug acme-ui --entry ./src/index.ts — useful in scripts, on machines without the desktop app, or when you want to script the whole onboarding. --repo-root and --framework are both required (no silent defaults); on a TTY without them you'll be prompted, off-TTY the CLI errors pointing at the missing flag.

When run interactively, import also asks four short questions about how to install your component dependencies (where to run install, which package manager, whether to skip install) — confirm the defaults or override. After registration succeeds, the CLI prints the path to _design-systems/_status.md so you can watch the first-build progress.

Add --wait to block until the first build reaches a terminal verdict; the CLI exits with 0 / 1 / 124 for clean / failed / timeout — the right shape for scripts and coding agents. statecraft live list shows every kind: live design system registered on this machine.

To change install, bundler, or engine config later, edit the committed statecraft.yaml and save — the daemon rebuilds. There is no live update verb; the manifest is the edit surface. statecraft live build --slug acme-ui manually nudges a rebuild without modifying source. statecraft live status --slug acme-ui (also --wait-capable) queries the current build verdict. statecraft live rm --slug acme-ui removes a registration without deleting the design system from the server (the editor UI keeps ownership of deletes so a stray CLI run can't drop a teammate's work).

One repo, multiple workspaces. The same component repo can publish to more than one workspace at a time — handy for a personal sandbox alongside your team workspace, or for contractors working with two clients off one library. Just re-run the wizard against the second workspace; both registrations live in the same .statecraft/live.json sidecar (each a thin pointer to its statecraft.yaml), and the daemon publishes each independently.

Publishing from CI instead? Teams whose component library is touched by many people across many branches often prefer a CI-driven publish — no laptop daemon required, runs in the same environment as your prod build. Register the design system once with statecraft import (above), commit the statecraft.yaml manifest, then wire a GitHub Actions / Buildkite / Jenkins job to run statecraft publish --manifest … on every push. See Keep components in sync for the workflow file + token setup.

React or Vue

Statecraft supports both. Each design system declares framework: react or framework: vue in its YAML and is filtered against the project's framework when a designer creates a project — Vue DSes for Vue projects, React DSes for React projects. For Vue, pass --framework vue on statecraft import and point --entry at a .ts barrel that imports your .vue SFCs (or directly at a .vue file).

Styling pipelines

Tell Statecraft once how your library produces styles and the daemon wires the matching plugin into its Vite toolchain. Every option is a per-DS flag (or YAML field) — never auto-detected, because past guessing rabbit-holed users in monorepo subpackages.

  • Tailwind v3 or v4. Pass --tailwind v3 --tailwind-css ./path/globals.css --tailwind-config ./path/tailwind.config.ts (or --tailwind v4 --tailwind-css ./path/globals.css) on import. The daemon adds @tailwindcss/vite (v4) or wires the PostCSS plugin (v3) into the scratch build.
  • Build-time CSS-in-JS — eight engines covered: vanilla-extract, linaria (via @wyw-in-js/vite), pigment-css, stylex, compiled, macaron, windi-css, panda. Pass --css-engine vanilla-extract (or whichever you use) on import and the daemon installs the matching plugin into the scratch toolchain. For StyleX and Windi the daemon also auto-imports the engine's virtual CSS module (virtual:stylex.css / virtual:windi.css) into the bundle entry. Panda additionally needs --panda-config ./panda.config.ts for its pre-build codegen step plus --css-engine-import ./src/styles/index.css pointing at your @layer directive CSS file (Panda has no canonical default path).
  • UnoCSS uses its own dedicated unocss.build: YAML block — pass --unocss-config ./uno.config.ts on import, optionally --unocss-preset uno --unocss-preset icons and --unocss-globals-css ./src/styles/globals.css. The daemon adds unocss to the scratch, wires the Vite plugin before react(), and auto-imports virtual:uno.css.
  • Hybrid runtime/extractkuma-ui. Default mode runtime-injects (works out of the box); static-extract mode is opt-in via --css-engine-import.
  • Runtime CSS-in-JSemotion, styled-components, stitches, goober, jss, theme-ui, fela, plus Compiled (its babel plugin compiles to a runtime). All work without a plugin chain — the host singletons them across every iframe via the importmap. Declaring them via --css-engine only changes how Statecraft reports a 0 KB CSS bundle (from "warning" to "expected").
  • Plain CSS / SCSS / CSS Modules / Less / Stylus work out of the box — Vite handles them natively, no engine entry needed.

Monorepo? Point install at the workspace root. If your components live in a pnpm/yarn/npm workspace subpackage and the lockfile is one or two directories up, you'll need to point dependency install at the workspace root. This is required for pnpm catalog: deps and workspace:* protocol refs — they only resolve where pnpm-workspace.yaml lives. The desktop app's Design systems panel detects this on the first failed build and offers a one-click Use workspace root button. From the CLI, pass --install-cwd .. on import (or however many ../ levels) — or fix it after the fact by setting install.installCwd in your statecraft.yaml and saving.

npm install crashing? Try pnpm. npm 11 on Node 22+ has known resolver bugs that can hit fine-looking package.json files — the headline one is an arborist crash on dep trees that pull rollup's platform-optional native binaries. The fix: set install.packageManager: pnpm (or yarn) in your statecraft.yaml. pnpm and yarn resolve the same tree cleanly; corepack ships with Node 22+ so neither needs a separate install. The switch only affects Statecraft's build — your own dev workflow stays on whatever you had unless you commit a new lockfile. The same --package-manager flag is available on the initial import. When the build status shows the arborist error specifically, the panel surfaces this fix directly; generic npm install failures point at the same flag as the first thing to try.

Populating the components catalog

Once the build is green — the Design systems panel shows Live · published for that row, or _status.md reports Status: OK — there's one more step before your components are fully usable in the canvas: populate components: in the published YAML.

Both scope: (the list of names usable in templates) and components: (the palette catalog) are hand-authored. Statecraft writes the bundle's named exports to _design-systems/<slug>.exports.txt in the sync folder as a copy-from list, but neither field is auto-populated — the runtime treats scope: as the single source of truth, and the palette can only render rich entries (snippet, props, category) that a human supplied. We can't fabricate those — the right snippet for <Card> usually has children, your Button's variant values are project-specific, the category groupings reflect your team's mental model.

While components: is empty, the editor flags it loudly: _status.md shows Status: WARNING — palette empty with the list of scope entries that have no matching catalog block, the toolbar's component palette renders an empty-state listing the bundled exports, and _reference.md shows the same warning under each affected DS heading. CSS-only / Tailwind-only design systems can legitimately ship without a components: block, so this is a warning rather than a failure — but pairing a populated scope: with empty components: is almost always an authoring half-step.

Open <sync-folder>/<workspace>/_design-systems/<slug>.yaml and add a components: block. Each entry should match a name in scope::

scope:
  - Button
  - Card
  - FormInput

components:
  - label: Button
    tagName: Button
    category: Form
    snippet: '<Button variant="solid">Click</Button>'
    props:
      - name: variant
        type: string
        label: Variant
        options:
          - { label: Solid, value: solid }
          - { label: Ghost, value: ghost }

  - label: Card
    tagName: Card
    category: Layout
    snippet: |
      <Card>
        <p>...</p>
      </Card>

  - label: Form input
    tagName: FormInput
    category: Form
    snippet: '<FormInput placeholder="Email" />'

The Core gallery starters (shadcn, radix-ui, ant-design, mui) are good references for richer schemas — install any of them from the gallery and open the row on the Design Systems page to see the full catalog YAML.

From a private repo

For libraries kept in a private repo where you don't want a public bundle, install the desktop tray (or the standalone statecraft CLI) and run statecraft import against the repo root. The daemon watches the working tree, rebundles on every save, and uploads the JS+CSS pair to Statecraft storage — no public hosting or HTTPS URL needed. Collaborators render the last-published bundle even when your machine is offline.

Monorepo / pnpm-catalog setups: pass --install-cwd, --install-command, or --package-manager at import time. The optional frameComponent field names a provider wrapper the bundle should re-export (<ThemeProvider>, route mocks); the iframe wraps every rendered state in it. Build failures surface in the workspace's _design-systems/_status.md with a classified hint.

The npm-package source kind in detail

The npm package starter (mentioned above) is a design-system source kind that takes a package name and an exact version — no bundle, no daemon, no CI:

name: Chakra UI
source:
  npm: "@chakra-ui/react"
  version: "2.8.2"

scope:
  - Button
  - Box
  - Stack

providers:
  - import: ChakraProvider

styleCache:
  kind: emotion

Exact semver only. Ranges like ^1.2.3 and latest are rejected. A compromised upstream cannot propagate automatically because every upgrade is a deliberate change to the version string.

Packages resolve through esm.sh at render time — there's no build step in Statecraft. The package needs to be ESM-compatible and work with the React version Statecraft ships. Good for public libraries you don't need a custom build of; for anything custom or private, use statecraft import.

Things that just work — no config needed

A few categories of components routinely surprise people with whether they'll render. Most of them are handled automatically:

Modals, popovers, and tooltips. Statecraft renders each state inside its own sandboxed iframe — and the design system runs inside that frame. So components that portal to document.body — modals, dialogs, popovers, dropdown menus, tooltips, toasts — render normally: their portal lands in the frame's own body, not the editor. Headless UI, Radix, MUI, and Ant Design overlays all work, each contained to its own state frame, and a library can never leak styles or overlays into the editor chrome or into another state.

Routing components. Many real components reach for the app's router — a Next <Link>, a react-router <Link to="/profile">, a wouter useLocation. In Statecraft there's no real router to navigate (each state is a static snapshot), but those components still need something above them to read context from. The canvas mounts inert no-op providers automatically so they render without crashing:

  • react-router-dom v6+ — wrapped in a <MemoryRouter>. <Link>, useLocation, useNavigate, useParams all work; clicks don't actually navigate.
  • wouter — wrapped in an in-memory <Router>. Same story: hooks resolve, links render, nothing navigates.
  • next/link, next/navigation, next/router — redirected to inert shims (the real Next.js packages only work inside next dev / next build). <Link> renders as an <a>; useRouter, usePathname, useSearchParams return "no current route". redirect() and notFound() throw a clear error.

No design-system configuration needed — just import and use. The one case that does need configuration is @tanstack/react-router: declare its <RouterProvider> via your DS YAML's providers: chain (or the bundle's frame component) so tanstack hooks resolve.

When loading fails

When loading fails, the canvas shows a resolver error in place of the preview. The most common causes are covered in the YAML reference's "Common esm.sh errors" section. For kind: live design systems, _design-systems/_status.md is the canonical "did the last build work?" check — read it before assuming anything's deeper than a YAML typo.