为了账号安全,请及时绑定邮箱和手机立即绑定

Tailwind 4中的类型安全设计令牌探索

Tailwind 4 已经计划了一段时间,团队在 2024 年 3 月 开源了他们的进展。最值得注意的变化之一是从基于 JavaScript 的配置转向了基于 CSS 的配置,在我看来。目前 Tailwind 4 正在测试版阶段,据我所知,团队仍在解决一些问题,特别是在 Safari 兼容性方面。

注:稍后在文章中,我们将假设您正在使用基于组件的框架/库,但所讨论的概念可以轻松应用于其他方法或框架,这样更流畅且含义更明确。

尾风 4 的更新

迁移到 CSS 配置文件。

我听说有些人抱怨,尤其是来自 TypeScript 用户的抱怨。但是,Tailwind 4.0 的路线图将优先支持对经典的 tailwind.config.js,作为其首要任务。

支持 JavaScript 配置文件 — 重新支持经典的 tailwind.config.js 文件,以使迁移到 v4 更加简单。

话说,这主要是为了迁移目的,并可能不是一个长期可行的解决方案。

这对类型安全来说意味着什么

Tailwind 4 使用新的 @property CSS 规则来定义内部自定义属性。

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

目前我无法在 VS code 中找到合适的 @property 规则语法高亮功能,我在 Bluesky 上发帖询问是否有人在这方面做得更好,看看是否有更好的解决方案。

我希望更好的 @property 支持能在将来帮助我们,关于这一点,我们以后再聊。

@属性 CSS 规则是什么意思

@property 规则允许直接在样式表中注册自定义属性,而无需运行任何 JavaScript 代码。有效的 @property 规则将注册一个自定义属性,这和使用 registerProperty() 函数来注册自定义属性类似。

什么是设计元素(tokens)?

现在我们已经介绍了 Tailwind 4 即将带来的变化及其可能的影响,让我们花点时间谈谈设计令牌(设计令牌是存储和管理设计决策的一种方法)。如果你不熟悉这个术语,这里有一个快速解释:设计令牌是一种以一致和可重用的格式存储和管理设计决策的方法。它们代表设计系统的关键视觉属性,如颜色、字体、间距、阴影等,并以结构化方式保存。目标是把这些设计值集中在一处,以便它们可以在不同的平台和工具之间轻松更新、维护和共享。

设计系统通常包含两种主要类型的值:系统值组件值。例如:你的系统值可能如下所示:

    const SYSTEM_TOKENS: ISystemTokens = {
      /* ... */
      COLORS: {
        /* ... */
        GREEN: {
          LIGHT: "#E0E5D9",
          中等色: "#3F6212",
          深绿色: "#28331A",
        }
        /* ... */
      },
      TYPOGRAPHY: {
        /* ... */
      }
      /* ... */
    }

全屏模式。退出全屏。

你可以在组件里的标记里引用系统值,比如这样:

import { SYSTEM_TOKENS } from "...";

const 按钮属性: IButtonTokens = {
  /* ... */
  颜色: {
    /* ... */
    背景: SYSTEM_TOKENS.颜色.绿色.深色,
    /* ... */
  },
  排版: {
    /* ... */
  }
  /* ... */
}

全屏模式 退出全屏

如果你对设计系统感兴趣的话,可以试试了解一下知名的系统,比如[Material Design(材料设计)],https://m3.material.io/foundations/design-tokens/overview

使用 Tailwind CSS 设计组件令牌

大约一周前,我写了一篇文章,讨论了用 Tailwind CSS 创建组件变体的另一种方法。简而言之,这篇文章探讨了如何利用 CSS 变量与 Tailwind 来处理复杂变体,通过动态组件属性和变量映射直接设置变体值。如果你想了解我是如何想到这种方法的,可以在这里阅读更多内容:用 Tailwind CSS 写组件变体的不同方法

我们应该从识别依赖设计令牌的部分开始。正如我们之前提到的,这将包括颜色、字体、间距以及其他任何对您的设计至关重要的固定系统值。我们来看一下这个不使用设计令牌的Button 组件

    <button class="p-4 bg-red text-white rounded-lg relative flex justify-center">点我</button>

进入全屏 退出全屏

在上面的例子中,我们可以识别出几个可以被分词化或标记化的值。以下每一个类都可能对应于我们设计体系中的一个值:

  • p-4 边距为4个单位
  • bg-red 背景为红色
  • text-white 文本颜色为白色

既然我们已经确定了可以分词的值,我们可以将它们分为两类:静态值动态值。静态值是指在组件中保持不变的值,而动态值是指可以通过传递给组件的属性来变化的值。例如,在我们的示例中,我们将使内边距(p-4)保持不变,而文本颜色(text-white)和背景(bg-red)则应通过theme属性动态设置。

生成令牌

Tailwind 4框架配置文件

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

    @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;
    }

全屏模式,退出全屏

系统 token (系统令牌)

接下来我们需要创建名为 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-medium";
          DARK: "--color-green-dark";
        },
        RED: {
          LIGHT: "--color-red-light";
          MEDIUM: "--color-red-medium";
          DARK: "--color-red-dark";
        }
      },
      SPACING: {
        SMALL: "--spacing-sm";
        MEDIUM: "--spacing-md";
      }
    }

全屏模式 | 退出全屏

系统设计中的标记可以在设计中这样引用,如下所示:
$system.COLORS.GREEN.LIGHT

如果在一个理想的世界里,我们可以将 CSS 文件中的 @property 规则中的这些类型直接导出到我们的 TColorTSpacing 类型中,就像 SCSS 导入可以被转换成 JavaScript 一样。但根据我的了解,目前这还不可行。

组件令牌

现在我们已经实现了系统令牌功能,可以开始将它们整合到我们的组件中。第一步是设置我们的 <Component>.tokens.ts 文件(例如 <Button>.tokens.ts)。为了说明这一点,我们来看一个例子,让我们以之前讨论的 Button 组件为例,创建一个对应的 Button.tokens.ts 文件。

概括来说,我们的 Button 组件结构如下:

<button class="p-4 bg-red text-white rounded-lg relative flex justify-center" aria-label="点我">点我</button>

全屏模式:点击这里可以进入/退出全屏

之前,我们讨论了静态值(如 p-4)和动态值(如 bg-redtext-white)的区别。这种区别将指导我们如何组织设计令牌。静态属性,如 p-4,应放在 STATIC 下,而动态属性,如 bg-redtext-white,应根据其属性标识符分组。在这种情况下,由于我们通过 theme 属性来设置 bg-redtext-white,因此,它们应该放在标记文件的 THEME 部分。在我们的例子中,我们假设有两个主题变量 - PRIMARYSECONDARY

    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: IButtonThemeTokens = {
      PRIMARY: {
        backgroundColor: "--color-red-dark",
        textColor: "--color-red-light",
      },
      SECONDARY: {
        backgroundColor: "--color-green-dark",
        textColor: "--color-green-light",
      },
    };

全屏 退出全屏

组件设计标记可以在设计中像这样引用:$component.Button.THEME.PRIMARY.backgroundColor。我更喜欢这样的命名约定:
$component.组件名.属性值.tokenName

$component:区分 $system$component 代号
<ComponentName>:遵循组件的内部文件命名约定
PROP_NAME:属性名称应使用常量命名
PROP_VALUE:属性值应遵循内部命名约定
Token 名称 (如 backgroundColor):应使用驼峰命名*

这完全是个人喜好的问题,你来决定什么最适合你的工作流程。

  • 命名令牌时,如果需要为元素的状态(如 :hover)指定一个令牌,我会稍微偏离驼峰命名规则。在这种情况下,我通常会在令牌名称前加上状态,后面跟两个下划线,例如:hover__背景颜色

在组件中使用设计 token

正如我在前面的文章中提到的,我之前写过一篇关于用 Tailwind CSS 编写组件变体样式的另一种方法的文章(探索一种用 Tailwind CSS 编写组件变体样式的替代方法)。本文会引用那篇文章中的方法,如果你还没看过,建议先读一下,这有助于你理解这种方法的背景。

在这里假设你使用一个JavaScript框架或库来创建组件。

更新一下 Button 组件

我们需要用由css变量支持的Tailwind类来替换现有的标记类。请注意,变量名称与我们按钮组件中的两个标记接口 IButtonStaticTokens 接口和 IButtonThemeTokens 接口中的名称相匹配;

    <button class="p-[--padding] bg-[--backgroundColor] text-[--textColor] rounded-lg relative flex justify-center">点我</button>

进入全屏模式
退出全屏模式

更新了类之后,我们现在需要动态地应用组件样式并更新相关变量。为此,我们将在组件上使用一个 variableMap 函数。该函数将我们从 Button.tokens.ts 中的 token 映射到组件上的内联 CSS 变量,这样我们的类就可以直接引用这些变量了。有关变量映射的示例请参阅,文章的末尾有更多相关信息,请参阅 这篇文章

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

    const props = /*THEME*/
    </script>

全屏 退出全屏

最后我们来总结一下

我期待 Tailwind 4 的发布,看看团队在这段时间里会做出哪些改动。我一直享受尝试各种方法来解决设计令牌、变体和类型安全方面的挑战。

这是一个实验性的方法,肯定会引发一些强烈的反馈。

如果你觉得这篇文章有趣或有用,请关注我 BlueskyMediumDev 和/ 或 Twitter

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消