Back

Back

Token Updater — Figma ↔ GitHub sync

Commits design tokens from Figma straight to GitHub and applies incoming changes through a diff preview, so you see what breaks before accepting — 39 commits since launch.

Role

Design & Build

Industry

Design tooling

status

In use

Year

2026

Challenge

Grid's tokens live in Figma and have to end up in code. The normal version of that is a person: someone exports, someone reformats, someone opens a PR, and somewhere in the chain a hex gets retyped wrong or a dark-mode value gets missed.

Grid makes it harder than the usual case. The tokens are layered across four collections — Global holds raw primitives, M3 - Light and M3 - Dark alias into Global, and Designer carries Light and Dark modes aliasing into M3. Three tiers deep, and the value a developer needs is at the bottom of an alias chain that crosses collection boundaries and changes depending on which mode you're resolving for.

A tool that exports the top layer literally produces references to references. A tool that resolves them carelessly produces light-mode colours in the dark theme.

Describe this image here

Process

One commit, not four

The easy way to write files to GitHub is the Contents API — PUT /contents/{path}, one call per file. It works, and it produces four separate commits for one token change. For the seconds between them, the repo holds a token set that doesn't agree with itself.

So the push uses the low-level Git Data API instead: read the branch ref, read its tree, create a blob per changed file, build a new tree on top of the base, create one commit with the branch head as parent, then move the ref.

Six calls instead of one, to get a property that matters — the token set enters the repo as a single atomic change, or not at all. A developer pulling mid-push can't get a half-updated system.


Mode-aware alias resolution

Resolving a Designer token to an actual hex means walking an alias chain into M3 and then into Global. The subtlety is what happens at the boundary.

A Designer variable has Light and Dark modes. The M3 variables it points at don'tM3 - Light and M3 - Dark are separate collections, each with a single mode. So carrying the Designer mode ID down the chain resolves to nothing, or silently to the wrong value.

resolveHexForMode handles it: it uses the passed mode where the target has it, and switches to the target collection's own first mode when it crosses a boundary. Depth-capped at 10 so a circular alias can't hang the plugin.

This is the bug that would have shipped light-mode colours into the dark theme, and it isn't visible until someone builds the app.


Warn on divergence, don't block

A Designer token whose Light and Dark modes alias to different targets is usually a mistake — but occasionally deliberate. The plugin collects these as warnings and surfaces them with a dismissable notice rather than refusing to push.

Same principle as the audit tool: the designer knows things the plugin doesn't.


Speak an existing format

Output is Tokens Studio JSON — aliases as {gray.True.900}, values wrapped as { value, type }. Not a format I invented, so the pipeline plugs into tooling that already exists on the engineering side.

Getting there meant normalising Figma's naming. Type prefixes come off (color/gray/True/900gray/True/900), and so do group prefixes where the leaf already repeats the group (radius/radius4radius4, font/size/size11size11). Semantic type is inferred from Figma's own variable scopes — a FLOAT scoped to CORNER_RADIUS becomes borderRadius, one scoped to GAP or padding becomes spacing. The designer already declared intent by scoping the variable; the plugin reads it rather than guessing from the name.


A real diff, not a confirmation dialog

Before any push, the plugin fetches the four files from the branch, base64-decodes them, flattens both sides to dot-paths, and compares. Added, modified, and removed rows, per file, old value struck through next to the new one.

Only files that actually changed get blobs. Push is disabled when the diff is empty.

Showing what will change before it changes is the reason this is safe to run. It's the same reason code review exists, brought into the design tool.


Fail with an answer

If the four collections aren't found, the error names which ones are missing and says "Make sure you are in the Grid Design System file." If the Designer collection lacks Light and Dark modes, the error lists the modes it did find.

Both are failures that would otherwise produce a confusing empty state.

Describe this image here

Outcome

39 token commits since launch. Design token changes reach the repo from inside Figma, as reviewable diffs, without a manual export-and-reformat step in between.

Describe this image here

What I Learned

  • The mode boundary was the whole problem. Resolving aliases within one collection is trivial; the difficulty is that Designer has Light and Dark modes and the M3 collections it points into don't. Carrying the mode ID across that boundary silently produces wrong colours, and nothing catches it until it's built.

  • Six API calls beat one when the property you want is atomicity. The Contents API would have taken an afternoon and left the repo briefly inconsistent on every push. The Git Data API took considerably longer and means the token set is never half-updated.

  • Showing the change is what makes the tool safe to use. The diff isn't a feature on top of the sync — it's the reason anyone trusts pressing the button.

Back

Token Updater — Figma ↔ GitHub sync

Commits design tokens from Figma straight to GitHub and applies incoming changes through a diff preview, so you see what breaks before accepting — 39 commits since launch.

Role

Design & Build

Industry

Design tooling

status

In use

Year

2026

Challenge

Grid's tokens live in Figma and have to end up in code. The normal version of that is a person: someone exports, someone reformats, someone opens a PR, and somewhere in the chain a hex gets retyped wrong or a dark-mode value gets missed.

Grid makes it harder than the usual case. The tokens are layered across four collections — Global holds raw primitives, M3 - Light and M3 - Dark alias into Global, and Designer carries Light and Dark modes aliasing into M3. Three tiers deep, and the value a developer needs is at the bottom of an alias chain that crosses collection boundaries and changes depending on which mode you're resolving for.

A tool that exports the top layer literally produces references to references. A tool that resolves them carelessly produces light-mode colours in the dark theme.

Describe this image here

Process

One commit, not four

The easy way to write files to GitHub is the Contents API — PUT /contents/{path}, one call per file. It works, and it produces four separate commits for one token change. For the seconds between them, the repo holds a token set that doesn't agree with itself.

So the push uses the low-level Git Data API instead: read the branch ref, read its tree, create a blob per changed file, build a new tree on top of the base, create one commit with the branch head as parent, then move the ref.

Six calls instead of one, to get a property that matters — the token set enters the repo as a single atomic change, or not at all. A developer pulling mid-push can't get a half-updated system.


Mode-aware alias resolution

Resolving a Designer token to an actual hex means walking an alias chain into M3 and then into Global. The subtlety is what happens at the boundary.

A Designer variable has Light and Dark modes. The M3 variables it points at don'tM3 - Light and M3 - Dark are separate collections, each with a single mode. So carrying the Designer mode ID down the chain resolves to nothing, or silently to the wrong value.

resolveHexForMode handles it: it uses the passed mode where the target has it, and switches to the target collection's own first mode when it crosses a boundary. Depth-capped at 10 so a circular alias can't hang the plugin.

This is the bug that would have shipped light-mode colours into the dark theme, and it isn't visible until someone builds the app.


Warn on divergence, don't block

A Designer token whose Light and Dark modes alias to different targets is usually a mistake — but occasionally deliberate. The plugin collects these as warnings and surfaces them with a dismissable notice rather than refusing to push.

Same principle as the audit tool: the designer knows things the plugin doesn't.


Speak an existing format

Output is Tokens Studio JSON — aliases as {gray.True.900}, values wrapped as { value, type }. Not a format I invented, so the pipeline plugs into tooling that already exists on the engineering side.

Getting there meant normalising Figma's naming. Type prefixes come off (color/gray/True/900gray/True/900), and so do group prefixes where the leaf already repeats the group (radius/radius4radius4, font/size/size11size11). Semantic type is inferred from Figma's own variable scopes — a FLOAT scoped to CORNER_RADIUS becomes borderRadius, one scoped to GAP or padding becomes spacing. The designer already declared intent by scoping the variable; the plugin reads it rather than guessing from the name.


A real diff, not a confirmation dialog

Before any push, the plugin fetches the four files from the branch, base64-decodes them, flattens both sides to dot-paths, and compares. Added, modified, and removed rows, per file, old value struck through next to the new one.

Only files that actually changed get blobs. Push is disabled when the diff is empty.

Showing what will change before it changes is the reason this is safe to run. It's the same reason code review exists, brought into the design tool.


Fail with an answer

If the four collections aren't found, the error names which ones are missing and says "Make sure you are in the Grid Design System file." If the Designer collection lacks Light and Dark modes, the error lists the modes it did find.

Both are failures that would otherwise produce a confusing empty state.

Describe this image here

Outcome

39 token commits since launch. Design token changes reach the repo from inside Figma, as reviewable diffs, without a manual export-and-reformat step in between.

Describe this image here

What I Learned

  • The mode boundary was the whole problem. Resolving aliases within one collection is trivial; the difficulty is that Designer has Light and Dark modes and the M3 collections it points into don't. Carrying the mode ID across that boundary silently produces wrong colours, and nothing catches it until it's built.

  • Six API calls beat one when the property you want is atomicity. The Contents API would have taken an afternoon and left the repo briefly inconsistent on every push. The Git Data API took considerably longer and means the token set is never half-updated.

  • Showing the change is what makes the tool safe to use. The diff isn't a feature on top of the sync — it's the reason anyone trusts pressing the button.

Designed and built by Boku.
All rights reserved ©2026

Designed and built by Boku.
All rights reserved ©2026