Skip to main content

Design-to-code in CI

Problem: You have a canonical Moda canvas that represents your design tokens (colors, fonts, radii, spacing variables). On every push to main, regenerate tailwind.theme.ts (or equivalent) from the canvas and commit the diff, so code always tracks design.

Primitives

  • GET /v1/canvases/{id}/tokens — structured JSON of colors / fonts / radii / variables
  • A tiny codegen step in your CI (Node / Python / Deno)
  • Commit + PR if the diff is non-empty
Scope: designs:read only. No tasks:write, no designs:export. Create a minimal API key for CI.

TypeScript — GitHub Actions

.github/workflows/sync-theme.yml:
scripts/sync-theme.mjs:

Python — GitLab CI variant

.gitlab-ci.yml:
scripts/sync_theme.py:

Why tokens, not get_canvas

GET /v1/canvases/{id}/tokens is a dedicated endpoint that returns structured JSON. Parsing tokens out of the pseudo-HTML from GET /v1/canvases/{id} works but is fragile — layer names / HTML shape can change without signaling a token change. Use the dedicated endpoint for CI.

Making it diffable

  • Sort all arrays before emitting — otherwise insertion order in the canvas creates spurious diffs on every run.
  • Emit a deterministic timestamp header (or omit the timestamp entirely; git tells you when the file changed).
  • Name variables in the Moda canvas — named variables land as keys in the variables object and become your colors.primary, colors.background, etc.

Gotchas

  • Scope the API key narrowly. designs:read only. A leaked CI key with tasks:write / designs:export is a bigger blast radius.
  • Unknown response fields may appear over time. Don’t fail the build if the JSON has new keys — only fail if the keys you need are missing.
  • Cache-bust properly. If your codegen reads other files (a base theme, palette overrides), include them in the cache key for the CI action.
  • Don’t bypass PR review by committing directly to main. PR the change — design tokens can have visual fallout.
  • The canvas must be team-accessible to the API key’s team. Share links do NOT grant design token reads in CI — use a canvas URL + a team-scoped key.

See also