Skip to content

chore: sync tokens from Figma - #727

Merged
bastionstack merged 8 commits into
mainfrom
design-tokens/figma-sync
Aug 3, 2026
Merged

chore: sync tokens from Figma#727
bastionstack merged 8 commits into
mainfrom
design-tokens/figma-sync

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Automated design-token sync from the Zitadel Design System, plus the resolver change the new collections turned out to need.

Which Problems Are Solved

Figma ships four new variable collections: brand, Gradient Colors, heading-typography, Syntax.

The export resolver classified collections by shape — the Light/Dark one was the semantic colour surface, anything else multi-mode was viewport typography. Syntax and Gradient Colors are both Light/Dark and neither is semantic, so both landed in the viewport bucket keyed only by mode name, where the later-sorting file replaced the earlier one outright. Gradient Colors resolved, counted toward resolvedLeaves, and reached no output.

build.ts never read that bucket either, so none of the new colours reached a consumer.

The snapshot gate cannot catch either case: it runs against the built surface, so a discarded collection leaves it green.

How the Problems Are Solved

  • Roles are declared in packages/design-tokens/src/collections.ts (semantic | themed | viewport | primitives | registry-only) rather than inferred from shape. Every collection still feeds the alias registry, so {brand.purple.500} resolves even though brand surfaces nothing itself.
  • themed collections emit as --zl-syntax-* and --zl-gradient-* with [data-theme="light"] overrides and Tailwind aliases. They stay out of css/shadcn.css, which owns the unprefixed shadcn contract; that file is unchanged.
  • An unclassified collection falls back to registry-only and is reported in $source.unclassifiedCollections, where the resolver spec fails it. Deliberately not a throw — :sync-export runs before the workflow opens the PR, so throwing yields no check to go red.
  • Snapshot updated with 12 keys, additions only.

Additional Changes

None.

Additional Context

  • Two follow-ups belong in Figma rather than here, since figma-export/** is plugin-generated and hand edits revert on the next push: the Syntax variable descriptions name a Tailwind palette the values no longer use, and heading-typography types "APK Futural" in four places instead of linking font/font-serif. Detail in the resolved review threads.
  • The gradient stops are tokenised here but still hardcoded at their call sites; refactor: drive the account and pill gradients from Figma tokens #738 covers that.
  • .github/scripts/design-tokens-open-sync-pr.sh regenerates this body on every sync run, so a later designer push will overwrite it.

Copilot AI review requested due to automatic review settings August 3, 2026 11:42
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nextgen Ready Ready Preview Aug 3, 2026 3:32pm
nextgen-docs Ready Ready Preview Aug 3, 2026 3:32pm
nextgen-mock-zitadel Ready Ready Preview Aug 3, 2026 3:32pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot could not run the full agentic suite for this review because it was automatically requested on a bot-authored pull request. Request a review from Copilot under Reviewers to retry with the full agentic suite. Improved support for bot-authored pull requests is coming soon.

Adds new design token collections exported from Figma (brand palette, gradients, heading typography, and syntax highlighting colors) and updates the generated token bundle accordingly.

Changes:

  • Expanded generated figma.tokens.json metadata and added light/dark color.syntax tokens.
  • Added new Figma export JSON files for brand, Gradient Colors, heading-typography, and Syntax.
  • Updated resolved token leaf count to reflect the new collections.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/design-tokens/src/generated/figma.tokens.json Updates generated tokens/metadata to include new collections and syntax colors.
packages/design-tokens/figma-export/syntax.json Introduces syntax highlighting tokens mapped to brand palette for Dark/Light modes.
packages/design-tokens/figma-export/heading-typography.json Adds heading typography tokens (currently only font-family).
packages/design-tokens/figma-export/gradient-colors.json Adds gradient color tokens for Light/Dark modes.
packages/design-tokens/figma-export/brand.json Adds a brand color palette used by other token collections via references.

Comment thread packages/design-tokens/figma-export/syntax.json
Comment thread packages/design-tokens/figma-export/heading-typography.json
`sync-from-export` inferred each collection's role from its shape: the
Light/Dark collection was the semantic colour surface, and anything else
multi-mode was viewport typography. Once Figma shipped `Syntax` and
`Gradient Colors` — both Light/Dark, neither semantic — both landed in
the viewport bucket keyed only by mode name, and whichever file sorted
last replaced the other outright. `Gradient Colors` resolved, was counted
in `resolvedLeaves`, and then left the pipeline without a trace.

Roles are now declared in `src/collections.ts`, keyed by Figma collection
name: semantic | themed | viewport | primitives | registry-only. An
export the manifest does not name stops the sync, as does a manifest
entry whose collection no longer exists, so adding or renaming a
collection in Figma is a decision someone makes rather than one the
resolver guesses. Within a role, two collections reaching for the same
key throws instead of overwriting. Every collection still feeds the alias
registry regardless of role, so `{brand.purple.500}` resolves even though
`brand` surfaces nothing itself.

`themed` collections now reach consumers: `--zl-syntax-*` and
`--zl-gradient-*` with `[data-theme="light"]` overrides, plus Tailwind
aliases (`text-zl-syntax-key`, `bg-zl-gradient-red-start`). They are kept
out of `css/shadcn.css`, which owns the unprefixed shadcn contract.

The snapshot test cannot catch either failure — it only sees names that
already reached `build.ts` — so the resolver spec covers both.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

🦋 Changeset detected

Latest commit: d45bbd9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@zitadel/design-tokens Patch
@zitadel/docs Patch
@zitadel/ui-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

`:sync-export` runs before the workflow opens or updates the sync PR, so
throwing on an unclassified collection killed the run without ever
producing a check to go red. The only trace was a workflow log nobody
reads — the same invisibility that let the dropped gradient collection
through in the first place.

An unclassified export now falls back to `registry-only`, the
conservative role that surfaces nothing, and is reported in
`$source.unclassifiedCollections`; a manifest entry with no matching
export lands in `$source.staleCollectionRoles`. The exports still land as
a reviewable PR, and `sync-from-export.spec.ts` fails `full-pr` until
someone classifies them.

`semantic` still throws when zero or two collections claim it: there is
no conservative default for "which collection owns color.*".
@bastionstack

Copy link
Copy Markdown
Contributor

Review: this sync silently dropped a whole collection

Reviewed the token diff and the two Copilot comments. The exports are fine; the pipeline was not. Two commits added here fix it.

What was wrong

sync-from-export.ts classified collections by shape: "the Light/Dark collection is the semantic colour surface, anything else multi-mode is viewport typography". Figma has now shipped three Light/Dark collections (3. Mode, Syntax, Gradient Colors), so Syntax and Gradient Colors both landed in the viewport bucket keyed only by mode name — and typography[mode] = out is an assignment, not a merge. syntax.json sorts after gradient-colors.json, so it replaced it wholesale.

Gradient Colors resolved, was counted in resolvedLeaves (944 → 1006 is exactly the 62 new leaves), and then left the pipeline without a trace. That's the collection you'd least want to lose — its values are hardcodes already in the tree:

Token Value Hardcoded at
gradient.red.start + gradient.base.end #f25543, #0f0f11 apps/console/src/components/app-shell/AppShell.tsx:188
gradient.lavender.start #bba5e4 packages/components/src/orchestrator/templates/layout-chrome.css:254
gradient.neutral.start + base.end #484a57, #0f0f11 packages/shared-component-styles/src/pill.css:19

Separately, nothing in the original diff reached a consumer: build.ts declares typography in its input type but never reads it, so even the surviving syntax colours were unusable. tokens.css was byte-identical to main.

CI was green throughout, and structurally had to be — the snapshot gate runs against the built surface, so a discarded collection cannot turn it red.

What changed

  • Roles are declared, not inferredsrc/collections.ts maps each Figma collection to semantic / themed / viewport / primitives / registry-only. Every collection still feeds the alias registry regardless of role, so {brand.purple.500} resolves even though brand surfaces nothing itself.
  • themed collections now reach consumers--zl-syntax-* and --zl-gradient-* with [data-theme="light"] overrides, plus Tailwind aliases (text-zl-syntax-key, bg-zl-gradient-red-start). Kept out of css/shadcn.css, which owns the unprefixed shadcn contract; that file is byte-identical.
  • The gap is now a PR check, not a dead workflow. An unclassified collection defaults to registry-only and is reported in $source.unclassifiedCollections, with the resolver spec failing full-pr until it's classified. Deliberately not a throw: :sync-export runs before the workflow opens the PR, so throwing would kill the run with no check to go red — the same invisibility that caused this bug.

Snapshot updated with 12 added keys, reviewed as pure additions — no renames, no removals.

Figma-side follow-ups (cannot be fixed here)

figma-export/** is plugin-generated; hand edits revert on the next push. Both need changing at source:

  1. Syntax variable descriptions are leftovers from a Tailwind palette ("sky-400 / sky-600" etc.) while the values reference brand.*. Details in the resolved thread above.
  2. heading-typography hardcodes "APK Futural" four times instead of aliasing {font.font-serif}, which already resolves to that value — two sources of truth. All four carry only font-family, no size or line-height, so the collection currently adds nothing over 4. Custom.

Neither affects the pipeline; both are wrong provenance.

Still hardcoded

The three gradient sites above are now expressible as tokens but unchanged in this PR — that's a follow-up, since it touches console and login rendering and wants its own visual verification.

@bastionstack
bastionstack enabled auto-merge (squash) August 3, 2026 15:31
@bastionstack
bastionstack merged commit 04704fb into main Aug 3, 2026
14 checks passed
@bastionstack
bastionstack deleted the design-tokens/figma-sync branch August 3, 2026 15:41
bastionstack added a commit that referenced this pull request Aug 3, 2026
`Gradient Colors` now reaches consumers as `--zl-gradient-*`, so the two
places that hand-copied its values can reference them instead.

This is not colour-neutral. `--zl-gradient-base-end` and
`--zl-gradient-neutral-start` carry different light-mode values
(`#fafafa` / `#bfbfcf`) from the dark ones both sites hardcoded, so the
console account swatch and the pill sheen now follow the theme rather
than staying dark in light mode. That is what the tokens describe;
confirm it is what the design intends before merging.

`pill.css` uses `color-mix` to apply the 0.6 alpha, since `rgb()` cannot
take a hex custom property. That is a new idiom in this repo.

Leaves `layout-chrome.css` alone: its hero mark already references
`--zl-color-icon-default-purple` / `-pink`, and the gradient collection
has no equivalent pair (`lavender-start` and `rose-start` differ from
those tokens' light-mode values), so swapping would change colours
rather than tokenise them.

Depends on #727 for the variables to exist.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants