style-dictionary icon indicating copy to clipboard operation
style-dictionary copied to clipboard

composite typography fontFamily token reference gets interpreted as string

Open RobbyRabbitman opened this issue 1 month ago • 6 comments

From my understanding in the spec it says the fontFamily can be a value or token, however SD always transforms it as a string.

spec: fontFamily

Input:

{
  "heading-xxl": {
    "$type": "typography",
    "$value": {
      "fontFamily": "{Typography.Heading.Family Sans}",
      "fontSize": "{Typography.Heading.Size XXL}",
      "fontWeight": "{Typography.Heading.Weight Bold}",
      "fontStyle": "normal",
      "lineHeight": "{Typography.Line Height.LH 9}"
    }
  }
}

Output:

:root {
  --heading-xxl: normal var(--typography-heading-weight-bold)
    var(--typography-heading-size-xxl) /
    var(--typography-line-height-lh-9)
    'var(--typography-heading-family-sans)';
}

Expected output:

:root {
  --heading-xxl: normal var(--typography-heading-weight-bold)
    var(--typography-heading-size-xxl) /
    var(--typography-line-height-lh-9)
    var(--typography-heading-family-sans);
}

RobbyRabbitman avatar Nov 27 '25 09:11 RobbyRabbitman

+1 – I'm running into the same problem in my project.

The transform logic seems to unconditionally quote fontFamily values, without checking whether the resolved value is already a CSS function like var(). This leads to invalid CSS output.

According to the DTCG spec, fontFamily explicitly allows token references (aliases), not just string values. The current behavior violates this part of the specification.

Is there a recommended workaround, or would you accept a PR to fix the quoting logic in the typography transform?

jensoppermann avatar Nov 27 '25 10:11 jensoppermann

I can't seem to reproduce this easily, https://configurator.tokens.studio/#project=dVTBjtsgEP0VZFXaJkqc7WF7cE+tVts9N5V6qPeADXboErBgnMaN/O8dDM5CNsklwMy8N35v4JQdqLF5bW1WZJvlslRkSR41URoIZwIIE4bXIIcVgZ2wpBGSk7/UEtqDXrdccUOBs9zVbUpVqsJoLD3hnqzXtCB3X42gknyTtH69++KPq4J8+twdw64uyMP9fdgwDOUPYbPjlAnVro9HWWBHZo9A2O5HrFmERbXY+AWbT+gCq8dSZavMQs+Ezv9YrfDrpp7K7OfQ6dbQbjeUWeEbdcfPnis6c6dPdC/kQLZU2STiYh9g6Lg7LbNGK/CpZbZKcg5U9iEpEqLM5qTRL6a/ceU7rJLGIhom9lxZodWZJWVwonroGaq+BeU6/sVFu4OrWGhIDMNuwUih+PNtGLQyhon8vAUIkTtXACNrYs1d5enN2DyYmUf2jZEzvngr/gXSU/U+Ooszxesr1TDIUO4nM8mIdZkQ2Dh77q3GEcUBNbwVFrjZPj71qgZ01uK04qxirNaqEe3F8Frdm3qi/T2LEw25p3gJakvdJiofuKm0FRD0YryhvYR0XjrDO6Nrbq02NqEB/Yqzt/ZsKVEnKTSogU3o8Em5cAwM+uASvxvdd76LKSuWFt+XmNj9ols3DSO3IBSF6R44jPkFS+6e9wmNgTOReykErSaCi0zdTeJf3PEQ66Hr4QdvuOGq9t2BwXmM8sa3zXn5cun3+B8=

The output there is correct (partial snippet):

:root {
  --typography-heading-family-sans: 'Arial Black';
  --heading-xxl: normal var(--c) var(--b)/var(--d) var(--typography-heading-family-sans);
}

Can you post a reproduction?

jorenbroekema avatar Nov 28 '25 12:11 jorenbroekema

ahhh, we are missing the "$type": "fontFamily" in our primitive tokens - thanks for the help and sorry for the issue

RobbyRabbitman avatar Nov 28 '25 15:11 RobbyRabbitman

I respectfully disagree - this is a Style Dictionary bug, not a user configuration issue

While adding "$type": "fontFamily" to primitive tokens works as a workaround, I believe the underlying issue is still a bug in Style Dictionary. Here's why:

1. $type is optional according to DTCG spec

The Design Tokens Community Group specification states that $type can be inherited from group or referenced tokens. It's not required on every token.

2. Figma exports tokens without $type on references

When exporting Figma Variables, reference tokens typically don't have an explicit $type:

{
  "Typography": {
    "Heading": {
      "Family Sans": {
        "$value": "{Font Family.Sans}"
      }
    }
  }
}

This is standard behavior for Figma → Design Tokens workflows. Requiring users to manually add $type to every reference token breaks this workflow.

3. The bug is in createPropertyFormatter.js

The actual issue is in the reference replacement logic. When outputReferences: true:

  1. Font name Open Sans gets quoted → 'Open Sans' (correct)
  2. Reference replacement finds 'Open Sans' and replaces with var(--...)
  3. Bug: Only the inner value is replaced, not the surrounding quotes
  4. Result: 'var(--typography-heading-family-sans)' instead of var(--typography-heading-family-sans)

Workaround / Patch

For anyone hitting this issue, here's a patch for createPropertyFormatter.js (around line 226):

// When replacing object values, also check for quoted versions of the value
// This fixes typography tokens where fontFamily values are quoted (e.g., 'Open Sans')
// and need to be replaced with CSS variables without the surrounding quotes
const stringValue = `${value}`;
const quotedRefVal = `'${refVal}'`;
const doubleQuotedRefVal = `"${refVal}"`;

if (originalIsObject && stringValue.includes(quotedRefVal)) {
  value = stringValue.replace(quotedRefVal, replaceFunc());
} else if (originalIsObject && stringValue.includes(doubleQuotedRefVal)) {
  value = stringValue.replace(doubleQuotedRefVal, replaceFunc());
} else {
  value = stringValue.replace(
    originalIsObject ? refVal : new RegExp(`{${ref.path.join('\\.')}(\\.\\$?value)?}`, 'g'),
    replaceFunc,
  );
}

How to apply with different package managers

pnpm:

pnpm patch [email protected]
# Apply the fix above, then:
pnpm patch-commit <temp-folder-path>

npm/yarn (using patch-package):

npm install patch-package --save-dev
# Edit node_modules/style-dictionary/lib/common/formatHelpers/createPropertyFormatter.js
npx patch-package style-dictionary

jensoppermann avatar Nov 28 '25 17:11 jensoppermann

Yeah this is still a bug, agreed with @jensoppermann

Reproduction of the bug

The variable should not be in single quotes in the CSS output, regardless of whether the type is missing. Types are indeed completely optional, although somewhat required if you're expecting certain Style Dictionary transforms to do their job since transforms generally filter by token type. That said, outputting refs is something that happens in the format step and should work properly without $type being specified.

PR welcome!

jorenbroekema avatar Nov 30 '25 23:11 jorenbroekema

I've opened a PR and took a shot at fixing this.

cnaples79 avatar Dec 03 '25 03:12 cnaples79