[ 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.

[ 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.

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 that 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 — including a native iOS/Android app, where the token layer ported cleanly to NativeWind even though the React components couldn't cross the runtime. The values travel; the components don't.

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 ]

Taulu

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 ]