Skip to content

Add JSON typography and size map exports to brand-primitives - #1394

Open
stefankp wants to merge 3 commits into
mainfrom
stefankp/functional-tokens-json-export
Open

Add JSON typography and size map exports to brand-primitives#1394
stefankp wants to merge 3 commits into
mainfrom
stefankp/functional-tokens-json-export

Conversation

@stefankp

@stefankp stefankp commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two per-type JSON token maps to @primer/brand-primitives, mirroring the color map export merged in #1370: lib/design-tokens/json/typography.json and lib/design-tokens/json/size.json. Each is a one-dimensional map keyed by CSS custom-property name (--brand-* / --base-*) → value, reusing the existing json/one-dimensional Style Dictionary format.

These back downstream tooling (e.g. stylelint rules) that validates var(--…) references against the set of custom properties brand actually ships. It's a name-based allowlist, so each export is scoped to exactly match brand's shipped custom properties for that category. Colors were done in #1370; typography and size are next.

Example — typography.json:

{
  "--base-text-weight-bold": 700,
  "--brand-body-fontFamily": "'Mona Sans', 'MonaSansFallback',-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'",
  "--brand-fontStack-sansSerif": "'Mona Sans', 'MonaSansFallback',-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'",
  "--brand-heading-lineHeight-400": "1.3",
  "--brand-text-size-200": "1rem",
  "--brand-text-size-800": "2rem",
  "--brand-text-weight-600": "var(--base-text-weight-normal)"
}

Example — size.json:

{
  "--base-size-8": "0.5rem",
  "--base-size-16": "1rem",
  "--brand-borderRadius-medium": "0.5rem",
  "--brand-borderWidth-thin": "max(1px, 0.0625rem)",
  "--brand-box-spacing-normal": "1.5rem",
  "--brand-breakpoint-medium": "48rem",
  "--brand-control-medium-gap": "0.75rem"
}

List of notable changes:

  • added lib/design-tokens/json/typography.json (109 tokens) — base typography + functional responsive typography
  • added lib/design-tokens/json/size.json (76 tokens) — base size + functional size, breakpoints and border widths/radii
  • added src/filters/isSizeValue.js (+ unit tests) — value-shape predicate that restricts the size export to length-valued tokens
  • added two buildPrimitives steps in scripts/build-tokens.js for the typography and size exports

Scope decisions (intentional exclusions):

  • functional/size/viewport is excluded — it emits only @custom-media breakpoint definitions (0 custom properties), never referenced as var().
  • Animation values that live in the size sources (--brand-control-animation-duration, --brand-control-animation-easing) are dropped by isSizeValue — they belong to a future animation rule.
  • --brand-borderInset-* inset 0 0 0 … composites are dropped by isSizeValue — they'll belong to a future border rule.
  • The -auto pointer variants from size-fine/size-coarse are excluded — those files aren't imported by brand and the tokens are referenced 0× in brand CSS, so they aren't shipped custom properties.
  • Upstream functional/typography/typography.json is excluded — packages/react/src/css/stylesheets.ts doesn't import the CSS built from it, so its 43 tokens (the --brand-text-{body,title,caption,subtitle,display,code*}-* styles plus --brand-fontStack-sansSerifDisplay) never become shipped custom properties. Same criterion as the size-fine/size-coarse exclusion above. It's also what keeps --brand-fontStack-* on the Mona Sans values shown in the sample above — that file was the only thing redefining them to the generic system stack.

Note: values in size.json are resolved literals (e.g. --brand-borderRadius-medium: "0.5rem"), whereas typography and colors keep var() references where present — but the key/name set, which is what the allowlist consumes, is complete and correct.

What should reviewers focus on?

  • The token name-set in each JSON file matches brand's shipped --brand-*/--base-* custom properties for that category.
  • isSizeValue correctly keeps lengths / math functions / size var() refs and drops time, easing, inset … composites, and media-query ranges.

Steps to test:

  1. Run npm run build -w @primer/brand-primitives
  2. Confirm lib/design-tokens/json/typography.json (109 entries) and size.json (76 entries) are generated
  3. Run npm test -w @primer/brand-primitives — 29 tests pass (incl. the new isSizeValue suite)

Supporting resources (related issues, external links, etc):

Contributor checklist:

  • All new and existing CI checks pass
  • Tests prove that the feature works and covers both happy and unhappy paths
  • Any drop in coverage, breaking changes or regressions have been documented above
  • UI Changes contain new visual snapshots (generated by adding update snapshots label to the PR) — N/A, no UI changes
  • All developer debugging and non-functional logging has been removed
  • Related issues have been referenced in the PR description

Out of scope: pre-existing issue in colors.json

While cross-checking the typography map against brand's built CSS, I found one entry in the existing colors.json export that isn't a shipped custom property: --brand-blinking-cursor-fgColor. It's defined only in the vendored lib/design-tokens/css/tokens/functional/components/blinking-cursor/colors-with-modes.css, and nothing under packages/react/src imports that file — so the token is absent from main.css and from every per-component esm chunk.

This came in with the color map export in #1370 and predates this PR. I've deliberately left it alone to keep this diff focused; flagging it here for reviewers and for a follow-up. The other 516 entries in colors.json check out.

stefankp and others added 2 commits July 8, 2026 14:52
Mirror the color map export (#1370) with per-type json/one-dimensional
exports for typography and size tokens, written to
lib/design-tokens/json/{typography,size}.json.

Typography sources the two files brand ships in its CSS entry (base
typography + functional responsive typography); size sources base size,
functional size, breakpoints and border, and additionally filters by a new
isSizeValue value-shape predicate to drop non-size values (animation
easing/duration, inset border composites) that live alongside sizes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The typography allowlist is name-based, so it must cover every shipped
typography custom property. Source functional/typography/typography.json for
its --brand-text-body/title/caption/subtitle/display/code tokens and
--brand-fontStack-sansSerifDisplay (43 names present nowhere else), ordering
typography-responsive.json last so brand (Mona Sans) fontStack values win the
name collisions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 42e9a10

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

This PR includes changesets to release 9 packages
Name Type
@primer/brand-primitives Minor
@primer/brand-css Minor
@primer/react-brand Minor
@primer/brand-docs Minor
@primer/brand-e2e Minor
@primer/brand-fonts Minor
@primer/brand-mcp Minor
@primer/brand-config Minor
@primer/brand-storybook Minor

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

@stefankp
stefankp marked this pull request as ready for review July 8, 2026 14:13
@stefankp
stefankp requested a review from a team as a code owner July 8, 2026 14:13
Copilot AI review requested due to automatic review settings July 8, 2026 14:13
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🟢 No design token changes found

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🟢 Bundle size report

CheckMainBranchChange
UMD — full bundle (JS)101.39 kB101.39 kB🟢 No change
UMD — full bundle (CSS)66.97 kB66.97 kB🟢 No change
ESM — full bundle (JS + CSS)1.51 MB1.51 MB🟢 No change
ESM — tree-shaken simple (Button)69.94 kB69.94 kB🟢 No change
ESM — tree-shaken complex (ActionMenu)78.83 kB78.83 kB🟢 No change

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds two new one-dimensional JSON token map exports (typography + size) to @primer/brand-primitives, intended for downstream tooling to validate var(--…) references against the set of custom properties Brand actually ships—mirroring the existing colors.json export.

Changes:

  • Added isSizeValue predicate (and unit tests) to include only length-like size tokens and exclude animation/time/easing, inset composites, and media-query ranges.
  • Added new buildPrimitives steps to generate lib/design-tokens/json/typography.json and lib/design-tokens/json/size.json via the existing json/one-dimensional format.
  • Added a changeset bump (consistent with the earlier color-map export) to publish the new artifacts.
Show a summary per file
File Description
packages/design-tokens/src/filters/isSizeValue.js Adds the value-shape predicate used to select size/length tokens for the size JSON export.
packages/design-tokens/src/filters/isSizeValue.test.js Adds unit tests covering the intended include/exclude cases for size token values.
packages/design-tokens/scripts/build-tokens.js Adds build steps to emit json/typography.json and json/size.json using the existing one-dimensional JSON format and appropriate filtering/prefixing rules.
.changeset/brand-functional-token-maps-export.md Declares a release bump to publish the new JSON exports.

Review details

  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Low

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🟢 No visual differences found

Our visual comparison tests did not find any differences in the UI.

@stefankp

Copy link
Copy Markdown
Contributor Author

Worth calling out a change to the typography export, since it changes the shape of the output.

The map was originally built from three sources, including tokens/functional/typography/typography.json. That one is now excluded. packages/react/src/css/stylesheets.ts imports only base/typography/typography.css and functional/typography/typography-responsive.css — nothing imports the CSS built from the third file, so the 43 tokens it defines (--brand-text-{body,title,caption,subtitle,display,code*}-* plus --brand-fontStack-sansSerifDisplay) are never defined in the browser.

That matters more than the count. This export is consumed as a name-based allowlist, so including a token that's never defined green-lights var() references that fail at runtime. A reference to an undefined custom property makes the whole declaration invalid at computed-value time: it still wins the cascade and then computes to inherit, rather than falling through to a lower-precedence declaration. So font-size: var(--brand-text-caption-size) silently renders at the inherited size, and a @media override built on a dead token silently erases a valid base value.

The export goes from 152 → 109 tokens. I cross-checked the emitted key list against the typography primitives actually defined in the built main.css bundle: exact 1:1 match, nothing missing in either direction, and none of the 109 surviving values changed.

Two things this also cleans up:

  • The source-ordering workaround is gone. The --brand-fontStack-* collisions only existed because the legacy file redefined sansSerif / monospace / system with the old Apple system stack. With it removed, the two remaining sources have disjoint namespaces and order is irrelevant.
  • --brand-fontStack-sansSerifDisplay existed only in the legacy file, so nothing overrode it and it leaked the generic stack. It was visible in this PR's original sample output ("--brand-text-title-shorthand-large": "600 2rem/1.5 -apple-system, …"). It's now correctly absent.

The description has been updated to match.

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.

2 participants