Repository navigation
fix(tokens): export round trip: DESIGN.md spacing steps, typography aliases, no DTCG duplicates - #254
Merged
Merged
Conversation
…liases, no DTCG duplicates
The DESIGN.md export wrote Tailwind's spacing unit as `base: 0.25rem`,
which a DESIGN.md reader takes as the only step: read back, `padding:
"16px"` got "Use `p-[4px]`". It now writes the steps of the scale
(`"0.5": 0.125rem` to `"96": 24rem`) from the project's unit.
A typography alias was written as `"{typography.font-sans}"`, a reference
to a whole level, which onsystem's reader could not resolve and Google's
linter read as 22 unknown properties. It is now written as the level.
A DTCG file exported into the project is read back by zero config next
to the stylesheet, and its `@theme inline` tokens, which have no custom
property, were added a second time (42 tokens became 50). mergeTokens
now merges a token without a custom property by its name and Tailwind
key.
…in scale order A DTCG file listed before the stylesheet read -0.025em as other, and the merged token kept that, so tracking-tight left the DESIGN.md typography. Steps the project sets itself are now written in scale order with the generated ones. docs/tokens.md no longer says the read-back gives every spacing step: v4's other steps are off the scale for a project that reads only the DESIGN.md.
Merged
This was referenced Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap item: OS2-52 (token export round trip).
What was wrong
The export-then-import pair that
docs/tokens.mdadvertises gave wrong results in three ways. Each was reproduced on main with a stock shadcn/ui theme (test/fixtures/shadcn-neutral/globals.css):export --format design-mdwrote Tailwind's spacing unit asspacing: { base: 0.25rem }. DESIGN.md has no multiplier: its spec reads every spacing entry as a step of the scale, so a reader (onsystem included) took 4px as the only step. With that DESIGN.md as the only token source,checksaidpadding: "16px"→ "Usep-[4px](4px)",gap-[8px]→ nearestgap-[4px], andp-[13px]→ nearestp-[4px].--font-heading: var(--font-sans)was written asfont-heading: "{typography.font-sans}". A front matter reference must name a single value, not a whole typography level. onsystem's own reader warned "DESIGN.md: unresolved reference {typography.font-sans}".@google/design.md@0.4.0 lintreported 22 warnings, one per character of the string ("'0' is not a recognized typography property", and so on).**/*.tokens.json, soexport --format dtcg --out design.tokens.json(the command in the docs) is read back next to the stylesheet. Its@theme inlinetokens have no custom property, somergeTokenscould not match them, andinspectwent from 42 tokens to 50 (radius 8 → 14, typography 2 → 4). A later DESIGN.md export then listed the radii twice and warned six times "radius-… is left out of the front matter: another token has its key".What changed
src/tokens/export.ts: the DESIGN.md export writes Tailwind's spacing unit as the steps of Tailwind v3's default scale, which v4 still makes:"0.5": 0.125rem…"96": 24rem, computed from the project's unit. A step the project sets itself (--spacing-4) keeps its own value. The section is aMap, so"0.5"stays before"1"; a plain object puts integer-like keys first. A typography alias is written as the level it names (fontFamily: …), and other aliases stay references as before. The Layout table is unchanged, and still explains the unit andp-4.src/tokens/index.ts(mergeTokens): a token with no custom property and a Tailwind key now merges with the token of the same name and key, as tokens with a custom property already merged. The key leaves out the category on purpose: an exported-0.025emletter spacing has no DTCG type and reads back as categoryother. The name keepsleading-tightandtracking-tightapart, although they share the keytight.docs/tokens.md: says how@theme inlinetokens merge, that an export written into the project is read back without adding tokens, how the spacing unit and typography aliases are written to DESIGN.md, and whatcheckdoes with that file. It also says the test suite runs the DESIGN.md round trip.Why export the steps instead of reading
baseas a multiplierThe finding offered both. A reader-side rule ("a lone
baseis a multiplier") would disagree with the DESIGN.md spec, which usesbase: 16pxas an ordinary step next toxs…xl. It would also fix only onsystem: Google's linter and its Tailwind export would still see one 4px step. Writing the steps makes the file correct for any reader. A DESIGN.md token names no class, so a length on the scale is now left alone, and one off it gets the nearest step as an arbitrary value (p-[13px]→p-[12px]), asdocs/tokens.mdalready describes. A DESIGN.md written by an earlier version is read as before, with 4px as its only step; exporting again rewrites it, and the changeset says so.Outside the listed files
The plan for this item listed
docs/tokens.mdandsrc/tokens/{design-md,dtcg,export}.ts. The DTCG duplicates cannot be fixed in those files: the reader sees one file at a time, and the merge happens inmergeTokensinsrc/tokens/index.ts. The change there is the merge key, about six lines. No open pull request and no later item on the roadmap touches that file.design-md.tsanddtcg.tsneeded no change.How it was tested
New tests, each of which fails on main:
test/export.test.ts, "export, then read back":gap-[8px] p-[13px],style={{ padding: "16px" }}) in that app and in a project that has only the DESIGN.md. From the CSS, the fixes aregap-2,p-3andp-4. From the DESIGN.md, the only finding isp-[13px]→ nearestp-[12px]. On main, all three were 4px.--font-heading: var(--font-sans)) is written as afontFamilylevel, and reads back with no warning.checkfindings are unchanged. On main the second export wrote 50 tokens, with warnings.base: 0.25rem.test/tokens.test.ts:@theme inlinetokens (--radius-sm,--leading-tight,--tracking-tight) merge with the DTCG tokenstoDtcgwrites for them. A token with the same key but another name (leading-tightagainsttracking-tight) stays separate.By hand, with the built CLI on a copy of the stock app: the DESIGN.md-only check now reports only
p-[13px](nearestp-[12px]) androunded-[7px](nearestrounded-[6px], documented behaviour for DESIGN.md radii).inspectshows 42 tokens before and after the DTCG export, and the DESIGN.md export after it prints no warnings.@google/design.md@0.4.0 linton the export with a real font: 22 warnings on main, 0 on this branch.Gate:
pnpm install --frozen-lockfile --prefer-offline,pnpm format:check,pnpm lint,pnpm typecheck,pnpm test(105 files, 1359 passed, 3 skipped),pnpm buildandpnpm smokeall pass.mergeTokensruns for every project, so I also ran the real-world corpus (node scripts/corpus.mjs, after the build). It printed "Counts match corpus/snapshot.json", and all 80 labels of the blind sample are still reported; the snapshot is unchanged.