探索 Tailwind 4 中的 Typesafe 设计令牌

2025-06-07

探索 Tailwind 4 中的 Typesafe 设计令牌

Tailwind 4 的开发已近在眼前,团队于 2024 年 3 月首次开源了其开发进度。在我看来,最值得关注的变化之一是从基于 JavaScript 的配置转变为基于 CSS 的配置。Tailwind 4 目前处于测试阶段,据我了解,团队仍在应对一些挑战,尤其是在 Safari 兼容性方面。

注意:在本文的后面,我们将假设您正在使用基于组件的框架/库,但所讨论的概念很容易转移到其他方法。

Tailwind 4 的变化

转向 CSS 配置

我听到过一些关于这一点的抱怨,尤其是来自 TypeScript 用户的抱怨。不过,Tailwind 4.0 的路线图确实将对经典版本的支持列为tailwind.config.js首要任务:

支持 JavaScript 配置文件——重新引入与经典 tailwind.config.js 文件的兼容性,以便轻松迁移到 v4。

话虽如此,但这似乎主要是为了迁移目的,可能不是一个可持续的长期解决方案。

这对类型安全意味着什么

在底层,Tailwind 4 使用新的@propertyCSS 规则来定义内部自定义属性。

我们用@property适当的类型和约束来定义我们的内部自定义属性

目前,我无法@property在 VS 代码中找到对规则的体面语法高亮支持,我已经联系了Bluesky,看看是否有人比我更幸运。

我希望@property将来能有更好的支持来帮助我们,稍后我会详细介绍。

什么是 @property CSS 规则

@property规则表示直接在样式表中注册自定义属性,无需运行任何 JavaScript。有效的 @property 规则会注册自定义属性,类似于registerProperty()使用等效参数进行调用。

什么是设计令牌

既然我们已经介绍了 Tailwind 4 即将推出的变更及其潜在影响,那么接下来让我们来聊聊设计令牌 (Design token)。如果您不熟悉这个术语,这里有一个简短的解释:设计令牌是一种以一致、可重复使用的格式(通常作为变量)存储和管理设计决策的方法。它们以结构化的方式表示设计系统的关键视觉属性,例如颜色、排版、间距、阴影。目标是将这些设计值集中起来,以便它们能够在不同的平台和工具之间轻松更新、维护和共享。

设计系统通常由两种主要类型的值组成:System valuesComponent values。例如,您的系统值可能如下所示:

const SYSTEM_TOKENS: ISystemTokens = {
  /* ... */
  COLORS: {
    /* ... */
    GREEN: {
      LIGHT: "#E0E5D9",
      MEDIUM: "#3F6212",
      DARK: "#28331A",
    }
    /* ... */
  },
  TYPOGRAPHY: {
    /* ... */
  }
  /* ... */
}
Enter fullscreen mode Exit fullscreen mode

然后,您可以在组件令牌中引用系统值,如下所示:

import { SYSTEM_TOKENS } from "...";

const BUTTON_VALUES: IButtonTokens = {
  /* ... */
  COLORS: {
    /* ... */
    BACKGROUND: SYSTEM_TOKENS.COLORS.GREEN.DARK,
    /* ... */
  },
  TYPOGRAPHY: {
    /* ... */
  }
  /* ... */
}

Enter fullscreen mode Exit fullscreen mode

如果您有兴趣了解有关设计系统的更多信息,那么值得探索一下像Material Design这样的知名系统

使用 Tailwind 构建组件设计令牌

大约一周前,我写了一篇文章,讨论了我使用 Tailwind CSS 创建组件变体的另一种方法。简而言之,本文探讨了如何结合使用 CSS 变量和 Tailwind 来管理复杂的变体,并通过动态组件 props 和变量映射在内联中设置变体值。如果您对我如何想到这种方法感到好奇,可以在这里阅读更多相关信息:使用 Tailwind CSS 编写组件变体的另一种方法

我们应该首先确定组件中依赖于设计令牌的部分。如前所述,这将包括颜色、字体、间距以及设计中不可或缺的任何其他固定系统值。让我们看一下以下不依赖设计Button component令牌的情况:

<button class="p-4 bg-red text-white rounded-lg relative flex justify-center">Click me</button>
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,我们可以精确地指出几个可以标记的值。以下每个类都可以对应我们设计系统中的一个值:

  • p-4
  • bg-红色
  • 文本白色

现在我们已经确定了可以标记的值,我们可以将它们分为两类:static valuesdynamic值。静态值是组件中保持不变的值,而动态值是可以根据传递给组件的 props 而变化的值。在我们的示例中,我们将 padding ( p-4) 设为静态,而文本颜色 ( text-white) 和背景 ( bg-red) 则应通过 props 动态设置theme

创建代币

Tailwind 4 配置

首先,我们需要System tokens在新的 Tailwind CSS 配置中定义:

@import "tailwindcss";

@theme {
  --color-white: #FFFFFF;
  --color-green-light: #E0E5D9;
  --color-green-medium: #3F6212;
  --color-green-dark: #28331A;
  --color-red-light: #F4CCCC;
  --color-red-medium: #D50000;
  --color-red-dark: #640000;

  --spacing-sm: 1rem;
  --spacing-md: 2rem;
}
Enter fullscreen mode Exit fullscreen mode

系统代币

接下来我们需要创建system.tokens.ts文件:

export type TColor = "--color-white" | "--color-green-light" | "--color-green-medium" | "--color-green-dark" | "--color-red-light" | "--color-red-medium" | "--color-red-dark";

export type TSpacing = "--spacing-sm" | "--spacing-md";


interface ISystemTokens {
  COLORS: {
    WHITE: TColor;
    GREEN: {
      LIGHT: TColor;
      MEDIUM: TColor;
      DARK: TColor;
    },
    RED: {
      LIGHT: TColor;
      MEDIUM: TColor;
      DARK: TColor;
    }
  },
  SPACING: {
    SMALL: TSpacing;
    MEDIUM: TSpacing;
  }
}

export const  SYSTEM_TOKENS: ISystemTokens {
  COLORS: {
    WHITE: "--color-white";
    GREEN: {
      LIGHT: "--color-green-light";
      MEDIUM: "--color-green-light";
      DARK: "--color-green-light";
    },
    RED: {
      LIGHT: "--color-red-light";
      MEDIUM: "--color-red-medium";
      DARK: "--color-red-dark";
    }
  },
  SPACING: {
    SMALL: "--spacing-sm";
    MEDIUM: "--spacing-md";
  }
}
Enter fullscreen mode Exit fullscreen mode

系统设计标记可以在如下设计中引用:
$system.COLORS.GREEN.LIGHT

理想情况下,我们可以直接从 CSS 文件的 @property 规则中导出类型到我们的TColorTSpacing类型中,就像 SCSS 导入可以转换为 JavaScript 一样。不幸的是,据我所知,目前这还无法实现。

组件标记

现在我们已经实现了系统令牌,可以开始将它们集成到组件中了。第一步是设置<Component>.tokens.ts文件。为了说明这一点,我们以之前查看过的 Button 组件为例,创建一个相应的Button.tokens.ts file

回顾一下,我们的 Button 组件的结构如下:

<button class="p-4 bg-red text-white rounded-lg relative flex justify-center">Click me</button>
Enter fullscreen mode Exit fullscreen mode

之前,我们讨论了静态值(如p-4)和动态值(如bg-redtext-white)之间的区别。这种区别将指导我们如何组织设计 token。静态属性(如p-4)应归入STATIC,而动态属性(如bg-redtext-white)应归入相应的 prop 标识符。在本例中,由于我们通过prop 控制bg-red,因此它们应放在tokens 文件中的 部分下。在我们的示例中,我们假设有两个主题变量 -text-whitethemeTHEMEPRIMARYSECONDARY

import { SYSTEM_TOKENS, TColor, TSpacing } from "./system.tokens.ts";
import { TTheme } from "./Button"; // PRIMARY, SECONDARY

interface IButtonStaticTokens {
  padding: TSpacing;
}

interface IButtonThemeTokens {
  backgroundColor: TColor;
  textColor: TColor;
}

export const STATIC: IButtonStaticTokens {
  padding: "--spacing-sm";
}

export const THEME: IButtonStaticTokens {
  PRIMARY: {
    backgroundColor: "--color-red-dark";
    textColor: "--color-red-light";
  },
  SECONDARY: {
    backgroundColor: "--color-green-dark";
    textColor: "--color-green-light";
  };
}
Enter fullscreen mode Exit fullscreen mode

组件设计标记可以在设计中引用,如下所示:$component.Button.THEME.PRIMARY.backgroundColor。我倾向于使用以下命名约定:
$component.<ComponentName>.<PROP_NAME><PROP_VALUE>.tokenName

$component:区分$system$component标记
<ComponentName>:遵循组件的内部文件命名约定
PROP_NAME:常量大小写
PROP_VALUE:应遵循内部 prop 值大小写
Token name (backgroundColor):驼峰式大小写*

这最终是一个个人喜好的问题,由您来决定什么最适合您的工作流程。

  • 在命名 token 时,如果需要为元素的状态指定 token,例如 ,我会稍微偏离驼峰命名法:hover。在这种情况下,我会在 token 名称前加上状态前缀,后跟两个下划线,如下所示:hover__backgroundColor

在组件中使用设计令牌

正如我在文章前面提到的,我之前写过一篇关于探索使用 Tailwind CSS 编写组件变体的不同方法的文章。我将在这里引用该方法,所以如果你还没有读过,最好先阅读一下,以了解该方法背后的背景。

本文的这一部分将假设您使用 Javascript 框架或库来构建组件。

更新按钮组件

我们需要用由 CSS 变量支持的 Tailwind 类替换现有的可标记类。请注意,变量名称与我们的两个 Button 组件标记接口中的变量名称一致,IButtonStaticTokens并且IButtonThemeTokens

<button class="p-[--padding] bg-[--backgroundColor] text-[--textColor] rounded-lg relative flex justify-center">Click me</button>
Enter fullscreen mode Exit fullscreen mode

现在我们已经更新了类,我们需要动态地应用组件样式并更新变量。为此,我们将variableMap在组件上使用一个函数。本质上,这个函数将我们的标记映射到组件上的内联 CSS 变量,然后我们的类就可以引用这些变量。有关变量映射的示例,请参阅本文Button.tokens.ts末尾

<template>
  <button
    :style="[variableMap(STATIC), variableMap(THEME[props.THEME])]"
    class="p-[--padding] bg-[--backgroundColor] text-[--textColor] 
    rounded-lg relative flex justify-center"
  >
    Click me
  </button>
</template>
<script setup lang="ts">
import { variableMap } from "...";
import { STATIC, THEME } from "Button.tokens.ts";

const props = /*THEME*/
</script>
Enter fullscreen mode Exit fullscreen mode

结论

我期待着 Tailwind 4 的发布,以及团队在此期间做出的改进。我很享受尝试各种想法来应对设计令牌、变体和类型安全方面的挑战。

这是一种实验性的方法,我相信会有一些强烈的意见。

如果您发现这篇文章有趣或有用,请在Bluesky(我在这里最活跃)、MediumDev和/或Twitter上关注我。

文章来源:https://dev.to/wearethirdbears/exploring-typesafe-design-tokens-in-tailwind-4-372d
PREV
15 个超棒的 CSS 汉堡菜单
NEXT
使用 Tailwind CSS 构建 Shopify 主题 主题套件入门 安装 Tailwind 编辑输出 CSS 开发中的 TailwindCSS 生产中的 TailwindCSS 结论