JSON 设计令牌

背景

使用 JSON 等平台无关格式存储设计令牌的好处在于,它可以转换为或翻译成其他语言或工具(例如 CSS、Swift、Kotlin、Figma)。

快速入门

design-tokens.json 保留了我们在 mozilla-central 中设计令牌的真实来源,位于 design-system 文件夹中,位于 toolkit/themes/shared 下。该文件夹中的 CSS 设计令牌文件来自 JSON 文件。如果您需要修改设计令牌文件,则应编辑 JSON 文件。

为了能够在一个地方(JSON 文件)定义设计令牌,并允许所有平台以其特定格式使用设计令牌,我们使用了一个名为 Style Dictionary 的构建系统。

以下是为桌面构建设计令牌的方法

$ ./mach buildtokens

如果成功,您应该会看到 Style Dictionary 在 design-system 文件夹中构建了我们所有的令牌文件。否则,Style Dictionary 还可以生成有用的错误来帮助您进行调试。

最后,我们能够将 JSON 表示法转换为 CSS

{
 "color": {
    "blue": {
      "05": {
        "value": "#deeafc"
      },
      "30": {
        "value": "#73a7f3"
      },
      "50": {
        "value": "#0060df"
      },
      "60": {
        "value": "#0250bb"
      },
      "70": {
        "value": "#054096"
      },
      "80": {
        "value": "#003070"
      }
    },
  },
}
--color-blue-05: #deeafc;
--color-blue-30: #73a7f3;
--color-blue-50: #0060df;
--color-blue-60: #0250bb;
--color-blue-70: #054096;
--color-blue-80: #003070;

不错!

测试

我们已到位基本测试,以确保我们构建的 CSS 文件保持最新,并且新令牌被正确分类。如果修改了 JSON 但未运行构建令牌 CSS 文件的命令,或者直接修改了令牌 CSS 文件而未更改 JSON,则这些测试将失败。

我们的测试基于 Node,以便我们可以安装和使用 style-dictionary 库,并遵循之前用于 Devtools 和新标签页测试的格式。

您可以使用以下命令在本地运行测试

$ ./mach npm test --prefix=toolkit/themes/shared/design-system

JSON 格式深入

Style Dictionary 通过获取包含令牌数据的 JSON 文件并执行各种特定于平台的转换来输出针对不同语言格式化的令牌文件来工作。该库具有一些我们必须在提出用于表示令牌的 JSON 格式时考虑的怪癖和限制。以下是有关如何阅读和添加到 design-tokens.json 的指南,供任何需要使用我们的令牌或添加新令牌的人使用。

命名

从令牌名称到 JSON 时,我们在变量名称中插入连字符、下划线或大小写更改的每个位置都会转换为 JSON 中的嵌套层。这意味着具有 CSS 变量名称 --border-radius-circle 的令牌将表示为

"border": {
  "radius": {
    "circle": {
      "value": "9999px"
    }
  }
}

value 键是必需的,因为这是 Style Dictionary 查找的指示符,以了解 JSON 的哪些部分应解析为不同的令牌。它从最终的变量名称中省略。

对于简单情况,value 将是字符串或数字,但是当我们必须考虑媒体查询或主题时,value 将是对象。下面将详细解释这些案例的示例。

@base

在阅读我们的 JSON 时,您会看到很多地方我们使用了 @base 键,它始终出现在 value 键之前,表示该点之前的所有内容都应解析为令牌。例如,我们的 --font-weight CSS 令牌在我们的 JSON 中表示如下

"font": {
  "weight": {
    "@base": {
      "value": "normal"
    }
  }
}

这是针对 已知 限制 的解决方法,在该限制中,Style Dictionary 不支持出现在 value 键之后的嵌套令牌名称。如果我们想要同时拥有 --font-weight--font-weight-bold CSS 令牌,则需要将它们表示为具有其自身 value 的不同的对象

"font": {
  "weight": {
    "@base": {
      "value": "normal"
    },
    "bold": {
      "value": 600
    }
  }
}

@base 是我们选择的特殊指示符,以使 Style Dictionary 能够正确解析我们的令牌,同时仍然生成我们所需的变量名称。与 value 类似,它最终 被删除 出最终的令牌名称。

您还会看到很多地方 @base 在值定义中被引用。这是一个需要注意的地方——即使 @base 不是令牌名称的一部分,我们仍然需要在使用 Style Dictionary 的 变量引用/别名 语法时包含它。这意味着以下 CSS

--input-text-min-height: var(--button-min-height);

在我们的 JSON 中将如下所示

"input": {
  "text": {
    "min": {
      "height": {
        "value": "{button.min.height.@base}"
      }
    }
  }
}

高对比度模式 (HCM) 媒体查询

我们需要能够更改令牌值以同时支持 prefers-contrastforced-colors 媒体查询。我们在令牌的 value 键中嵌套使用 prefersContrastforcedColors 键来指示我们需要为该令牌生成媒体查询条目。例如,此 JSON

"border": {
  "color": {
    "interactive": {
      "@base": {
        "value": {
          "prefersContrast": "{text.color.@base}",
          "forcedColors": "ButtonText",
        }
      }
    },
  }
}

产生以下 CSS

/* tokens-shared.css */

@layer tokens-prefers-contrast {
  @media (prefers-contrast) {
    :root,
    :host(.anonymous-content-host) {
      /** Border **/
      --border-color-interactive: var(--text-color);
    }
  }
}

@layer tokens-forced-colors {
  @media (forced-colors) {
    :root,
    :host(.anonymous-content-host) {
      /** Border **/
      --border-color-interactive: ButtonText;
    }
  }
}

主题

我们的 JSON 支持围绕 Firefox 桌面客户端需求设计的两个主题维度;浅色和深色主题,以及品牌和平台主题。

浅色和深色主题

我们在 value 对象中使用 lightdark 键来指示何时给定令牌在我们的浅色和深色主题中应具有不同的值

"background": {
  "color": {
    "box": {
      "value": {
        "light": "{color.white}",
        "dark": "{color.gray.80}",
      }
    },
  }
}

上述 JSON 表明 --background-color-box 在我们的浅色主题中应具有 var(--color-white) 的值,在我们的深色主题中应具有 var(--color-gray-80) 的值。

如果令牌对浅色和深色主题都具有相同的值,则它将具有字符串或数字作为其 value,或者它将使用 default

"button": {
  "background": {
    "color": {
      "disabled": {
        "value": {
          "default": "{button.background.color.@base}",
        }
      }
    }
  }
}

上述 JSON 指示 --button-background-color-disabled 将具有 var(--button-background-color) 的值,无论主题如何。

品牌和平台主题

Firefox 桌面客户端由 两个不同的界面 组成;“浏览器 chrome”,即围绕网页的浏览器应用程序的 UI,以及通常称为“内容内”的 Web 内容本身。Firefox UI 开发跨越这两个界面,因为我们的 about: 页面是“内容内”页面。在我们的设计系统中,我们通过使用 platformbrand 的术语来区分这些界面。Chrome 特定的令牌值位于 tokens-platform.css 中,内容内特定的令牌值位于 tokens-brand.css 中。

我们在 JSON 中使用 platformbrand 键来指示何时令牌具有特定于界面的值。例如,此令牌定义

"text": {
  "color": {
    "@base": {
      "value": {
        "brand": {
          "light": "{color.gray.100}",
          "dark": "{color.gray.05}"
        },
        "platform": {
          "default": "currentColor"
        }
      }
    },
  }
}

这表明 --text-color 在 Chrome 表面应该在 tokens-platform.css 中的值为 currentColor,在内容页面应该在 tokens-brand.css 中的值为 light-dark(var(--color-gray-100), var(--color-gray-05))。生成的 CSS 代码分散在多个文件中。

/* tokens-platform.css */
@layer tokens-foundation {
  :root,
  :host(.anonymous-content-host) {
    /** Text **/
    --text-color: currentColor;
  }
}
/* tokens-brand.css */
@layer tokens-foundation {
  :root,
  :host(.anonymous-content-host) {
    /** Text **/
    --text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
  }
}

添加新的 Token

为了添加新的 Token,你需要修改 design-tokens.json 文件 - 基于我们的 JSON Token 定义生成的任何文件都不应该直接修改。你应该能够根据你在使用的任何语言中的变量名称反向推导出正确的 JSON 结构。

完成修改后,你可以通过运行 ./mach buildtokens 生成新的 Token CSS 文件。

如果在运行构建命令时遇到错误或得到意外的结果,可能是因为

  • 你忘记将所有内容嵌套在 value 键中,导致 Style Dictionary 未能正确解析你的 Token,或者;

  • 你在引用其他 Token 时省略了 @base,或者;

  • 你引用了尚未在 JSON 中定义的其他 Token - 在这种情况下,Style Dictionary 应该会给出描述性的错误消息。

如果你需要任何帮助,请随时在 Slack 上的 #firefox-reusable-components 或 Matrix 上的 #reusable-components 频道联系我们。