[ Case Study ]

Loom

A design-system pipeline that turns one config source into a Figma library and a React catalog, kept in lockstep.

// Every atom on this page came out of it

Open source — v2, validated across multiple products
  • React
  • Tailwind CSS
  • TypeScript
  • Figma Plugin API
Questionnaire
  • Primary color
  • Font pairing
  • Type scale
  • Spacing density
  • Edge style
Figma
  • Token variables
  • Text styles
  • Layout templates
  • Component sets
Next.js
  • tokens.css
  • React atoms
  • Per-atom manifests
  • Pick + install scripts

[ The Problem ]

Every time I started a new project the right way — modular UI, proper tokens, real components, dark mode, Figma parity — I spent the first few weeks on foundation work and never made it to the product.

The foundation kept eating the project.

Design and dev had to be set up twice: once in Figma, once in code, with manual translation between them. Every change meant updating both worlds and praying they didn't drift. A primary color tweak became a half-day of synchronization. I wanted to solve it once — a system where the design tokens, the Figma library, and the production code all came out of the same source.

That's Loom.

[ Architecture ]

One config source, two codegen pipelines, a catalog you pick from. Five pieces, start to finish.

  1. 01

    One config source

    One file you write by hand — spec/answers.json: brand colors, font pairing, spacing density, edge style, the type scale. About a dozen decisions, answerable in one sitting. It compiles to the base token configs in the spec/config/ directory, and everything downstream reads from those. Change an input, regenerate — nothing else gets hand-edited.

  2. 02

    Two pipelines, one source

    The same config feeds two codegen pipelines. assemble-figma.js builds the Figma file — variables, text styles, components. orchestrator.js builds the React catalog. Both read the identical JSON, so a brand color or a component spec is defined once and lands in both worlds.

  3. 03

    The component catalog

    The catalog spans eight groups — buttons, forms, layout, feedback, data display, navigation, composite, motion. Each atom is a .tsx component plus a .manifest.json declaring its dependencies, variants, and tokens. Visual treatment and color are orthogonal CVA (class-variance-authority) axes — filled / outline / ghost × brand / severity — so adding either is one line, not an N×M matrix. The four motion atoms are zero-dependency — hand-rolled on IntersectionObserver and requestAnimationFrame, no animation library.

  4. 04

    Pick what you need, own the copies

    You don't install the whole catalog. A loom-picks.json in your project lists the atoms you want; setup.shreads it, resolves each pick's dependencies transitively from the manifests — pick combobox and it pulls in popover and form-field— and copies just that subset into your project. After that the files are yours. It's the shadcn model: own the copies, no upstream dependency.

  5. 05

    Two-command install

    init.sh bootstraps the app shell once — ThemeProvider, root layout and fonts, the token substrate. setup.sh syncs the picked atoms and re-runs whenever the picks change. It prints the npm install line for whatever those atoms import rather than installing anything itself — your project owns its lockfile.

[ Decisions ]

One config source, two synchronized worlds. spec/answers.json compiles to the base token configs. The Figma pipeline reads them; the code pipeline reads them. Change a token, regenerate, both worlds update — Figma and code in lockstep. The cost is discipline: every change has to flow through the generator, so a new feature ships to both layers or neither. The payoff is that drift can't happen structurally, not just discouraged. There is no second source to drift from.

Open source, the shadcn way. Loom started as a private engine for my own projects. Going open in v2 was deliberate: the reusable part isn't my brand, it's the model — one source feeding two pipelines, a catalog you copy from instead of depend on. So it's public on GitHub under an MIT license, and consumption is shadcn-style: you pick atoms in loom-picks.json and own the copies — no npm installstep, no upstream sync. The cost is real: a fix to a catalog atom doesn't auto-propagate to projects that already picked it, and multi-project consistency takes deliberate re-picking. The payoff is zero lock-in — the atoms are yours the moment they land, with no version treadmill to stay on. Validated in production across multiple downstream products.

Paste-go Figma scripts, not a Figma plugin. The Figma side is thirty scripts pasted into the Plugin API console, not a packaged plugin. The payoff is portability and granularity: no plugin to install per account, no runtime, no permissions — and because each component script clears and rebuilds its own page on re-paste, you can update one family in isolation, re-running just the buttons script to change the buttons with siblings untouched. The cost is no GUI — a full build is the shared-utils helper plus the step scripts pasted in order, and the token/variable steps aren't idempotent, so rebuilding thoseonto an already-populated file means clearing it first. The whole "front end" stays plain text you can read and diff.

[ Under the Hood ]

JSON in, files out. The four slices below show the shape of the pipeline end to end — pick one.

The single source of truth. The questionnaire compiles to five JSON files — this is colors.json, the palette and role mapping every downstream step reads from.

> spec/config/base/colors.json
{
  "$note": "Generated from primary: #1E90FF, secondary: #1E90FF, accent: #1E90FF.
            Neutral tinted from primary hue (210°). Questionnaire is the override
            mechanism — change inputs and regenerate.",
  "palette": {
    "primary":   { "50": "#ecf2f9", "300": "#4da7ff", "500": "#006dd6", ... },
    "secondary": { ... },
    "neutral":   { "5": "#0c0d0e", "15": "#242629", "90": "#e4e6e7", ... },
    "success":   { ... },
    "warning":   { ... },
    "error":     { ... },
    "info":      { ... }
  },
  "roles": {
    "dark":  { "surface": "neutral.10", "on-surface": "neutral.90", ... },
    "light": { "surface": "neutral.95", "on-surface": "neutral.10", ... }
  }
}

[ More Case Studies ]

JAMIE

A persistent AI development partner built on agent harnessing and context engineering.

[ read case study ]

Availo

A digital bulletin board for spontaneous event planning — events are posters, your people and calendars are the boards.

[ read case study ]

Paperboy

A daily news dashboard that removes the friction and noise of scrolling.

[ read case study ]