web-styling-design-tokens

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Design Token Patterns

设计令牌模式

Quick Guide: A design token system is a one-way dependency graph: primitives hold raw values, semantic tokens name intent, component tokens hold local exceptions. Consumers only ever touch the top two tiers. Deliver tokens as CSS custom properties -- primitives in
:root
, semantic aliases per scope, component tokens reading semantics. Name tokens for their role, never for the mode they happen to appear in (
--color-surface-raised
, never
--color-dark-bg
). Store complete color values, never bare channel triplets. When tokens must exist in more than one language, generate every output from one DTCG source rather than hand-maintaining parallel copies.
Detailed Resources:
  • examples/core.md - Tier construction, aliasing, naming grammar, CSS custom property delivery,
    @property
    registration
  • examples/scales.md - Generated spacing and type series, modular scales, fluid type, density multipliers
  • examples/pipeline.md - DTCG source files, Style Dictionary platforms, typed TypeScript export
  • examples/utility-framework-bridge.md - Feeding tokens into a utility-class framework without duplicating the source of truth
  • reference.md - Naming grammar table, DTCG
    $type
    reference, format names, authoring checklist

<critical_requirements>
快速指南: 设计令牌系统是单向依赖图:基础令牌存储原始值,语义令牌定义用途,组件令牌存储局部例外。使用者仅需接触最上层两个层级。以CSS自定义属性形式交付令牌——基础令牌放在
:root
中,语义别名按作用域划分,组件令牌引用语义令牌。令牌命名需体现其作用,而非当前呈现的模式(例如
--color-surface-raised
,而非
--color-dark-bg
)。存储完整颜色值,切勿仅存储通道三元组。当令牌需支持多种语言/平台时,从单一DTCG源生成所有输出,而非手动维护多份副本。
详细资源:
  • examples/core.md - 层级构建、别名设置、命名语法、CSS自定义属性交付、
    @property
    注册
  • examples/scales.md - 生成式间距与字体序列、模块化比例、流式字体、密度乘数
  • examples/pipeline.md - DTCG源文件、Style Dictionary平台、类型化TypeScript导出
  • examples/utility-framework-bridge.md - 在不重复数据源的前提下,将令牌传入工具类框架
  • reference.md - 命名语法表、DTCG
    $type
    参考、格式名称、编写检查清单

<critical_requirements>

CRITICAL: Before Using This Skill

关键要求:使用此技能前须知

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST route every component style through a semantic or component token - NEVER let component CSS read a primitive token or a raw hex/px literal)
(You MUST name tokens for their role, NEVER for a mode or a literal value -
--color-surface-raised
, never
--color-dark-bg
or
--color-gray-900-bg
)
(You MUST store complete color values in tokens - NEVER bare channel triplets like
0 0% 100%
, which break
color-mix()
opacity math and
oklch()
interpolation)
(You MUST keep the dependency flow one-way: primitive -> semantic -> component - NEVER let a primitive reference a semantic token, and NEVER create a cycle)
(You MUST generate every additional output from one source when tokens exist in more than one language - NEVER hand-maintain a TypeScript constant beside a CSS variable)
</critical_requirements>

Auto-detection: design tokens, design token, token tiers, primitive tokens, semantic tokens, component tokens, token aliasing, token naming, token taxonomy, DTCG, Design Tokens Community Group,
$value
,
$type
, Style Dictionary, style-dictionary,
transformGroup
,
buildPath
,
css/variables
, CSS custom properties,
--color-
,
@property
, token pipeline, spacing scale, type scale, modular scale, density tokens
When to use:
  • Standing up the token layer a design system will sit on
  • Deciding how many tiers a token set needs and what belongs in each
  • Naming tokens so they survive a rebrand, a new mode, or a density change
  • Delivering tokens as CSS custom properties with correct scoping
  • Building tokens from a DTCG source into CSS, TypeScript, or other platform outputs
  • Generating spacing and type series instead of hand-listing every step
  • Auditing an existing token set for tier leaks, mode-named tokens, or drift between languages
When NOT to use:
  • A project with a handful of colors and no theming ambitions -- a short
    :root
    block is the right amount of structure
  • A single-surface prototype that will not outlive the sprint
  • A codebase whose values are already fully derived from an upstream system you do not own
This skill does NOT cover:
  • Runtime theme application, mode switching, and FOUC prevention -- how a theme gets selected, persisted, and applied without a flash is a separate concern (
    web-styling-theming
    ). This skill defines the token contract those mechanics switch between.
  • Utility-class authoring and usage -- writing markup with utility classes, variants, and responsive prefixes (
    web-styling-tailwind
    ). This skill covers where tokens are declared; that skill covers how the generated classes are used.
  • Component variant APIs -- mapping props to class combinations (
    web-styling-cva
    ). Variants consume component tokens; they do not define them.
Key patterns covered:
  • Token tiers: primitive -> semantic -> component, and when two tiers are enough
  • Naming grammar: category-property-variant-state, scale conventions, mode-free names
  • CSS custom properties as the delivery format, with scope-aware semantic aliases
  • @property
    registration where typed, animatable, non-inherited tokens earn it
  • Feeding tokens into a utility-class framework without forking the source of truth
  • DTCG-format source plus a build pipeline producing CSS and typed TypeScript
  • Scales as generated series, and density as a multiplier token

<philosophy>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入顺序、
import type
、命名常量)
(必须通过语义或组件令牌管理所有组件样式——绝对不能让组件CSS直接读取基础令牌或原始十六进制/像素字面量)
(必须根据令牌的作用命名,绝对不能以模式或字面量命名——例如
--color-surface-raised
,而非
--color-dark-bg
--color-gray-900-bg
(必须在令牌中存储完整颜色值——绝对不能仅存储
0 0% 100%
这类通道三元组,否则会破坏
color-mix()
透明度计算和
oklch()
插值)
(必须保持依赖流向单向:基础→语义→组件——绝对不能让基础令牌引用语义令牌,也不能创建循环依赖)
(当令牌需支持多种语言/平台时,必须从单一源生成所有额外输出——绝对不能手动维护TypeScript常量与CSS变量两份内容)
</critical_requirements>

自动检测关键词: design tokens, design token, token tiers, primitive tokens, semantic tokens, component tokens, token aliasing, token naming, token taxonomy, DTCG, Design Tokens Community Group,
$value
,
$type
, Style Dictionary, style-dictionary,
transformGroup
,
buildPath
,
css/variables
, CSS custom properties,
--color-
,
@property
, token pipeline, spacing scale, type scale, modular scale, density tokens
适用场景:
  • 搭建设计系统的令牌底层架构
  • 确定令牌集需要多少层级,以及各层级的内容划分
  • 为令牌命名,使其能适应品牌重塑、新模式添加或密度变更
  • 以正确的作用域交付CSS自定义属性形式的令牌
  • 从DTCG源构建令牌,输出到CSS、TypeScript或其他平台
  • 生成间距与字体序列,而非手动列出每一项
  • 审计现有令牌集的层级泄漏、模式命名令牌或跨语言差异
不适用场景:
  • 仅有少量颜色且无主题化需求的项目——简短的
    :root
    块已足够
  • 仅存活于当前迭代的单页面原型
  • 其值已完全来自上游系统且无法修改的代码库
本技能不涵盖:
  • 运行时主题应用、模式切换与FOUC预防——主题的选择、持久化与无闪烁应用是独立的关注点(对应
    web-styling-theming
    )。本技能定义的是这些机制切换的令牌契约
  • 工具类编写与使用——使用工具类、变体和响应式前缀编写标记(对应
    web-styling-tailwind
    )。本技能涵盖令牌的声明,而该技能涵盖生成类的使用
  • 组件变体API——将属性映射到类组合(对应
    web-styling-cva
    )。变体使用组件令牌,但不定义令牌。
核心模式涵盖:
  • 令牌层级:基础→语义→组件,以及何时仅需两层
  • 命名语法:类别-属性-变体-状态、比例约定、无模式名称
  • 以CSS自定义属性作为交付格式,带作用域感知的语义别名
  • 为可动画、非继承的令牌注册
    @property
  • 在不拆分数据源的前提下,将令牌传入工具类框架
  • DTCG格式源加上可生成CSS和类型化TypeScript的构建流水线
  • 作为生成序列的比例,以及作为乘数令牌的密度

<philosophy>

Philosophy

设计理念

A token system is not a list of values. It is a one-way dependency graph with a naming contract, and its whole purpose is to put a layer of indirection between "what the value is" and "what the value means" so the two can change independently.
The three tiers exist to separate three kinds of change:
TierAnswersChanges whenWho reads it
Primitive"What is this value?"The palette or scale is regeneratedSemantic tokens only
Semantic"What is this value for?"The design language shifts, or a modeComponents, and component tokens
Component"What does this part use?"One component needs a local exceptionThat component only
Core principles:
  • One-way flow. Primitive -> semantic -> component. A primitive that references a semantic token has inverted the graph and made the palette un-regenerable.
  • Consumers touch semantics, never primitives.
    background: var(--color-surface-raised)
    survives a rebrand.
    background: var(--color-gray-100)
    does not.
  • Names encode role, not appearance, and never mode. The moment a token is called
    --color-dark-bg
    , it can only ever be correct in one mode, and every additional mode forks the name.
  • Every tier you add is indirection you pay for on every read. Three tiers is the ceiling, not the target. Component tokens are exceptions, not a mirror of the component tree.
  • The token set is the contract; the delivery format is an implementation detail. CSS custom properties, a typed TypeScript object, and a native platform file are three renderings of the same graph -- which is exactly why they must be generated, not typed twice.
Why a value that appears once still deserves a semantic token: the question is not "how many places use this?" but "when this changes, will everyone who uses it want to change together?" A token is a statement that a set of usages share a fate.
When two tiers suffice: a product with a single brand, a single mode, and no white-labelling can collapse component tokens away and let components read semantics directly. Add the component tier the first time a component needs a value that genuinely diverges from the semantic default -- not preemptively.
</philosophy>
<patterns>
令牌系统不是值的列表,而是带有命名契约的单向依赖图,其核心目的是在“值是什么”和“值的用途是什么”之间增加一层间接性,使两者可独立变更。
三个层级用于区分三种变更场景:
层级回答的问题变更触发条件读取方
基础令牌“这个值是什么?”调色板或比例重新生成时仅语义令牌
语义令牌“这个值用于什么场景?”设计语言变更或模式切换时组件与组件令牌
组件令牌“这个组件的该部分使用什么值?”单个组件需要局部例外时仅该组件
核心原则:
  • 单向流向:基础→语义→组件。若基础令牌引用语义令牌,则依赖图反转,调色板无法重新生成。
  • 使用者仅接触语义令牌,绝不直接读取基础令牌
    background: var(--color-surface-raised)
    能在品牌重塑后继续生效,而
    background: var(--color-gray-100)
    则不能。
  • 名称编码作用,而非外观,更不能包含模式:一旦令牌被命名为
    --color-dark-bg
    ,它仅能在一种模式下生效,新增模式时必须创建新名称。
  • 每增加一层层级,都会增加读取时的间接成本:三层是上限,而非目标。组件令牌是例外情况,而非组件树的镜像。
  • 令牌集是契约,交付格式是实现细节:CSS自定义属性、类型化TypeScript对象、原生平台文件是同一依赖图的三种呈现——这正是必须通过生成而非手动编写多份内容的原因。
为何仅使用一次的值仍需要语义令牌: 关键问题不是“有多少地方使用这个值?”,而是“当这个值变更时,所有使用它的地方是否需要同步变更?”。令牌代表一组使用场景共享相同的变更逻辑。
何时仅需两层: 单一品牌、单一模式且无需白标的产品,可以省略组件令牌,让组件直接读取语义令牌。仅当组件确实需要偏离语义默认值时,再添加组件层级——不要提前添加。
</philosophy>
<patterns>

Core Patterns

核心模式

Pattern 1: Token Tiers and the One-Way Graph

模式1:令牌层级与单向依赖图

Primitives are the only tier holding literal values. Semantic tokens alias primitives and name intent. Component tokens alias semantics and exist only where a component diverges.
css
:root {
  /* Tier 1 - primitives: literal values, no meaning attached */
  --palette-slate-50: oklch(0.98 0.003 250);
  --palette-slate-900: oklch(0.21 0.02 250);

  /* Tier 2 - semantic: names intent, aliases a primitive */
  --color-surface-default: var(--palette-slate-50);
  --color-text-default: var(--palette-slate-900);

  /* Tier 3 - component: a local exception, aliases a semantic */
  --card-surface: var(--color-surface-default);
}
Key rules: a primitive never references anything, a semantic never holds a literal, a component token never reaches past the semantic tier. Circular references are a hard error in every conforming pipeline -- the graph must be acyclic.
Two tiers is a valid answer. Add the component tier at the first genuine divergence, not before. A component token that is a straight pass-through of a semantic token (
--card-surface: var(--color-surface-default)
with no override anywhere) is indirection that earns nothing -- delete it and let the component read the semantic directly.
See examples/core.md for a full three-tier set, the two-tier collapse, and the inverted-graph anti-pattern.

基础令牌是唯一存储字面量的层级。语义令牌别名基础令牌并定义用途。组件令牌别名语义令牌,仅在组件需要偏离默认值时存在。
css
:root {
  /* 第一层 - 基础令牌:字面量,无附加含义 */
  --palette-slate-50: oklch(0.98 0.003 250);
  --palette-slate-900: oklch(0.21 0.02 250);

  /* 第二层 - 语义令牌:定义用途,别名基础令牌 */
  --color-surface-default: var(--palette-slate-50);
  --color-text-default: var(--palette-slate-900);

  /* 第三层 - 组件令牌:局部例外,别名语义令牌 */
  --card-surface: var(--color-surface-default);
}
关键规则: 基础令牌绝不引用任何内容,语义令牌绝不存储字面量,组件令牌绝不直接引用基础令牌。符合规范的流水线会将循环依赖视为严重错误——依赖图必须是无环的。
两层结构是合理选择。仅在首次出现真正的偏离时添加组件层级,不要提前添加。如果组件令牌只是直接传递语义令牌的值(例如
--card-surface: var(--color-surface-default)
且无任何覆盖),这种间接性毫无意义——应删除它,让组件直接读取语义令牌。
详见examples/core.md中的完整三层结构、两层简化结构,以及反向依赖图的反模式。

Pattern 2: Naming Grammar

模式2:命名语法

A token name is a path read left to right, from most general to most specific. Fix the segment order once and every name in the system becomes predictable.
[namespace-]category-concept[-variant][-state][-scale]

--color-text-default            category=color  concept=text   variant=default
--color-text-danger-hover       + variant=danger + state=hover
--space-inline-sm               category=space  concept=inline scale=sm
--acme-color-surface-raised     namespace=acme (only when tokens ship outside the app)
Key rules: the category segment comes first so tokens sort into groups; state is always the last modifier so
-hover
and
-pressed
read consistently; a segment that is absent means "default" rather than being spelled out at every level.
Never bake the mode into the name.
--color-dark-bg
cannot be correct in light mode, so a second name has to exist, and now every consumer needs a conditional. The mode belongs in the scope that assigns the value, not in the identifier:
css
/* Bad - the name is only true in one mode */
--color-dark-bg: oklch(0.21 0.02 250);

/* Good - one name, value reassigned per scope */
--color-surface-default: var(--palette-slate-50);
[data-theme="dark"] {
  --color-surface-default: var(--palette-slate-900);
}
Scale conventions: numeric steps (
50
-
950
) for primitive ramps where the number is a position, t-shirt sizes (
xs
-
xl
) for semantic steps where the number would imply false precision. Do not mix the two conventions inside one namespace.
See examples/core.md for the full segment table, scale conventions, and the mode-in-name refactor.

令牌名称是从左到右的路径,从最通用到最具体。一旦确定段顺序,系统中所有名称都会变得可预测。
[命名空间-]类别-概念[-变体][-状态][-比例]

--color-text-default            类别=color  概念=text   变体=default
--color-text-danger-hover       + 变体=danger + 状态=hover
--space-inline-sm               类别=space  概念=inline 比例=sm
--acme-color-surface-raised     命名空间=acme(仅当令牌在应用外部发布时使用)
关键规则: 类别段放在最前面,以便令牌按组排序;状态始终是最后一个修饰符,确保
-hover
-pressed
的一致性;省略的段表示“默认”,无需在每个层级都拼写出来。
绝对不要在名称中包含模式
--color-dark-bg
在浅色模式下无效,因此必须创建第二个名称,且所有使用者都需要添加条件判断。模式应放在赋值的作用域中,而非标识符中:
css
/* 错误 - 名称仅在一种模式下有效 */
--color-dark-bg: oklch(0.21 0.02 250);

/* 正确 - 单一名称,按作用域重新赋值 */
--color-surface-default: var(--palette-slate-50);
[data-theme="dark"] {
  --color-surface-default: var(--palette-slate-900);
}
比例约定: 基础渐变使用数字步骤(
50
-
950
),数字代表位置;语义步骤使用T恤尺码(
xs
-
xl
),避免数字带来的虚假精度。同一命名空间内不要混合两种约定。
详见examples/core.md中的完整段表、比例约定,以及模式名称重构示例。

Pattern 3: CSS Custom Properties as the Delivery Format

模式3:以CSS自定义属性作为交付格式

Custom properties are the delivery format because they are the only one that participates in the cascade -- reassigning a semantic token inside a scope re-themes every descendant with no rebuild and no re-render.
css
:root {
  --palette-blue-600: oklch(0.55 0.19 258);
  --color-action-default: var(
    --palette-blue-600
  ); /* semantic, global default */
}

/* A scope reassigns semantics; primitives never move */
[data-density="compact"] {
  --space-inline-md: var(--space-2);
}

.card {
  --card-padding: var(--space-inline-md); /* component token reads semantic */
  padding: var(--card-padding);
}
Key rules: primitives live in
:root
and are never reassigned; semantic tokens are the only tier a scope selector touches; component tokens are declared on the component's own selector so they inherit into its subtree and nowhere else. Never assign a fallback in the consumer (
var(--color-x, #fff)
) -- a missing token should be visible, not silently papered over.
Register a token with
@property
when it is animated, or when unintended inheritance would be a bug.
Both
syntax
and
inherits
are required descriptors, and
initial-value
is required for any
syntax
other than
*
:
css
@property --card-elevation-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}
Registration buys typed interpolation (an unregistered custom property cannot be transitioned), plus a fallback to
initial-value
instead of an inherited garbage value when a computed value is invalid. It costs a declaration per token, so register selectively -- animated tokens and component-local tokens that must not leak, not the whole set.
See examples/core.md for scoping rules, inheritance traps, and
@property
registration in full.

自定义属性是理想的交付格式,因为它们是唯一参与级联的格式——在作用域内重新赋值语义令牌,无需重建或重新渲染即可为所有后代重新应用主题。
css
:root {
  --palette-blue-600: oklch(0.55 0.19 258);
  --color-action-default: var(
    --palette-blue-600
  ); /* 语义令牌,全局默认值 */
}

/* 作用域重新赋值语义令牌;基础令牌永不移动 */
[data-density="compact"] {
  --space-inline-md: var(--space-2);
}

.card {
  --card-padding: var(--space-inline-md); /* 组件令牌读取语义令牌 */
  padding: var(--card-padding);
}
关键规则: 基础令牌放在
:root
中且绝不重新赋值;语义令牌是唯一可被作用域选择器修改的层级;组件令牌在组件自身的选择器上声明,以便继承到其子树中,且不会泄漏到其他地方。绝不要在使用者中添加回退值(
var(--color-x, #fff)
)——缺失的令牌应可见,而非被静默掩盖。
当令牌需要动画或防止意外继承时,使用
@property
注册
syntax
inherits
是必填描述符,除
*
外的所有
syntax
都需要
initial-value
css
@property --card-elevation-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}
注册可实现类型化插值(未注册的自定义属性无法过渡动画),且当计算值无效时回退到
initial-value
,而非继承无效值。但每个令牌都需要一个声明,因此需选择性注册——仅注册可动画令牌和必须防止泄漏的组件局部令牌,而非全部令牌。
详见examples/core.md中的作用域规则、继承陷阱,以及完整的
@property
注册示例。

Pattern 4: The Utility-Framework Bridge

模式4:工具类框架桥接

When a utility-class framework consumes the token set, the CSS custom properties stay the single source of truth and the framework's theme block is a projection of them -- never a second place values are typed.
In Tailwind v4,
@theme
is a token-declaration surface: each variable in it generates utility classes and a standard CSS variable.
@theme inline
resolves the value directly into the generated utilities, which is what makes the mode-swappable bridge work:
css
@import "tailwindcss";

/* 1. Real tokens live here, mode-swappable, framework-agnostic */
:root {
  --app-canvas: oklch(0.98 0.003 250);
}
[data-theme="dark"] {
  --app-canvas: oklch(0.21 0.02 250);
}

/* 2. Projection - the framework consumes tokens, it does not define them */
@theme inline {
  --color-canvas: var(--app-canvas);
}
Key rules:
@theme
must be top-level -- it cannot be nested inside a selector or media query, which is precisely why the mode-swappable values live in
:root
/
[data-theme]
and only the alias goes in
@theme
. Without
inline
, the generated utility emits a variable reference that resolves at the wrong scope and the mode swap silently fails. Rule of thumb: put a token in
@theme
only when it should map to a utility class; everything else stays in
:root
.
Values must be complete colors. Opacity modifiers compile to
color-mix(in oklab, var(--color-canvas) 50%, transparent)
, which requires a real color -- a bare channel triplet (
0 0% 100%
) produces invalid CSS and the utility silently drops.
See examples/utility-framework-bridge.md for the full bridge, namespace resets, and the duplicated-source-of-truth anti-pattern.

当工具类框架使用令牌集时,CSS自定义属性仍是单一数据源,框架的主题块是令牌的投影——绝不能成为第二个值的编写位置。
在Tailwind v4中,
@theme
是令牌声明面:其中的每个变量都会生成工具类标准CSS变量。
@theme inline
会将值直接解析到生成的工具类中,这正是模式可切换桥接的工作原理:
css
@import "tailwindcss";

/* 1. 真实令牌在此处,支持模式切换,与框架无关 */
:root {
  --app-canvas: oklch(0.98 0.003 250);
}
[data-theme="dark"] {
  --app-canvas: oklch(0.21 0.02 250);
}

/* 2. 投影 - 框架使用令牌,而非定义令牌 */
@theme inline {
  --color-canvas: var(--app-canvas);
}
关键规则:
@theme
必须是顶级的——不能嵌套在选择器或媒体查询中,这正是支持模式切换的值放在
:root
/
[data-theme]
中,仅别名放入
@theme
的原因。如果省略
inline
,生成的工具类会在错误的作用域解析引用,模式切换会静默失败。经验法则:仅当令牌需要映射到工具类时才放入
@theme
;其他内容都放在
:root
中。
值必须是完整颜色。透明度修饰符会编译为
color-mix(in oklab, var(--color-canvas) 50%, transparent)
,这需要真实颜色——仅通道三元组(
0 0% 100%
)会生成无效CSS,工具类会被静默丢弃。
详见examples/utility-framework-bridge.md中的完整桥接、命名空间重置,以及重复数据源的反模式。

Pattern 5: DTCG Source and a Build Pipeline

模式5:DTCG源与构建流水线

A pipeline earns its keep the moment the same token must exist in a second language or a second platform. Source lives in DTCG format (
$value
/
$type
/
$description
,
{alias}
references), and every output -- CSS, typed TypeScript, native files -- is generated from it.
json
{
  "color": {
    "$type": "color",
    "palette": { "slate-50": { "$value": "#f8fafc" } },
    "surface": {
      "default": {
        "$value": "{color.palette.slate-50}",
        "$description": "Page background in the default mode"
      }
    }
  }
}
javascript
// style-dictionary config: one source, many platforms
export const config = {
  source: ["tokens/**/*.json"],
  platforms: {
    css: {
      transformGroup: "css",
      buildPath: "build/css/", // trailing slash is required
      files: [
        {
          destination: "tokens.css",
          format: "css/variables",
          options: { outputReferences: true }, // preserves var() aliasing
        },
      ],
    },
    ts: {
      transformGroup: "js",
      buildPath: "build/ts/",
      files: [{ destination: "tokens.ts", format: "javascript/es6" }],
    },
  },
};
Key rules:
$type
hoists to the closest ancestor group that declares it, so declare it once per group rather than on every token. The
{group.token}
alias always resolves to a whole
$value
-- property-level access needs JSON Pointer (
#/color/blue/$value
). DTCG (
$value
) and the original format (
value
) cannot be combined in a single instance -- pick one for the whole source tree.
outputReferences: true
is what keeps
var()
aliasing in the CSS output instead of flattening every semantic token to a literal.
When a pipeline does not earn its keep: one platform, one language, and hand-written CSS custom properties that nothing else consumes. A build step that produces exactly one file nobody else reads is cost with no payoff -- reach for it when a second consumer appears, not before.
See examples/pipeline.md for the full DTCG source tree, platform configs, typed TypeScript export, and the drift anti-pattern.

当同一令牌需支持第二种语言或平台时,流水线的价值便体现出来。源文件采用DTCG格式(
$value
/
$type
/
$description
{alias}
引用),所有输出——CSS、类型化TypeScript、原生文件——都从该源生成。
json
{
  "color": {
    "$type": "color",
    "palette": { "slate-50": { "$value": "#f8fafc" } },
    "surface": {
      "default": {
        "$value": "{color.palette.slate-50}",
        "$description": "默认模式下的页面背景"
      }
    }
  }
}
javascript
// style-dictionary配置:单一源,多平台
export const config = {
  source: ["tokens/**/*.json"],
  platforms: {
    css: {
      transformGroup: "css",
      buildPath: "build/css/", // 必须带末尾斜杠
      files: [
        {
          destination: "tokens.css",
          format: "css/variables",
          options: { outputReferences: true }, // 保留var()别名
        },
      ],
    },
    ts: {
      transformGroup: "js",
      buildPath: "build/ts/",
      files: [{ destination: "tokens.ts", format: "javascript/es6" }],
    },
  },
};
关键规则:
$type
会提升到最近的声明它的祖先组,因此每个组只需声明一次,无需在每个令牌上声明。
{group.token}
别名始终解析为完整的
$value
——访问属性级内容需要JSON指针(
#/color/blue/$value
)。DTCG(
$value
)与原始格式(
value
不能在同一实例中混合——整个源树必须选择一种格式。
outputReferences: true
用于在CSS输出中保留
var()
别名,而非将所有语义令牌展平为字面量。
何时无需流水线: 仅一个平台、一种语言,且手写的CSS自定义属性未被其他内容使用。仅生成一个无人使用的文件的构建步骤毫无价值——当出现第二个使用者时再引入流水线。
详见examples/pipeline.md中的完整DTCG源树、平台配置、类型化TypeScript导出,以及差异反模式。

Pattern 6: Scales as Generated Series

模式6:作为生成序列的比例

Spacing and type are series, not sets. Express the generator -- a base and a ratio, or a base and a multiplier -- so every step is derivable and no step can be individually wrong.
css
:root {
  --space-base: 0.25rem;
  --space-1: calc(var(--space-base) * 1);
  --space-2: calc(var(--space-base) * 2);
  --space-4: calc(var(--space-base) * 4);
  --space-8: calc(var(--space-base) * 8);
}
Density is a multiplier on the semantic tier, not a second scale. One multiplier token re-scales the whole system without touching a single component:
css
:root {
  --density-scale: 1;
}
[data-density="compact"] {
  --density-scale: 0.75;
}

:root {
  --space-inline-md: calc(var(--space-4) * var(--density-scale));
}
Key rules: the multiplier applies where primitives become semantics, so component tokens inherit density for free. Type scales follow the same shape with a ratio instead of a multiplier, and fluid steps use
clamp()
with the min and max both derived from the scale rather than hand-picked. A
calc()
chain nested more than about three levels deep gets hard to debug -- if that happens, generate the resolved values in the pipeline instead.
See examples/scales.md for modular type scales, fluid
clamp()
steps, density multipliers, and generated-vs-hand-listed comparisons.
</patterns>
<decision_framework>
间距与字体是序列,而非集合。表达生成器——基准值与比例,或基准值与乘数——使每一步都可推导,且不会出现单独错误的步骤。
css
:root {
  --space-base: 0.25rem;
  --space-1: calc(var(--space-base) * 1);
  --space-2: calc(var(--space-base) * 2);
  --space-4: calc(var(--space-base) * 4);
  --space-8: calc(var(--space-base) * 8);
}
密度是语义层级的乘数,而非第二个比例。单个乘数令牌可重新缩放整个系统,无需修改任何组件:
css
:root {
  --density-scale: 1;
}
[data-density="compact"] {
  --density-scale: 0.75;
}

:root {
  --space-inline-md: calc(var(--space-4) * var(--density-scale));
}
关键规则: 乘数应用在基础令牌转为语义令牌的环节,因此组件令牌可自动继承密度。字体比例遵循相同结构,使用比例而非乘数,流式步骤使用
clamp()
,且最小值和最大值都从比例推导,而非手动选择。嵌套超过三层的
calc()
链会难以调试——若出现这种情况,应在流水线中生成解析后的值。
详见examples/scales.md中的模块化字体比例、流式
clamp()
步骤、密度乘数,以及生成式与手动列出的对比。
</patterns>
<decision_framework>

Decision Framework

决策框架

How many tiers?

需要多少层级?

Does the product need more than one visual mode, brand, or tenant?
|-- NO --> Does any component need a value that diverges from the system default?
|   |-- NO  --> Two tiers: primitive + semantic
|   |-- YES --> Two tiers plus component tokens only for the divergent components
|-- YES --> Three tiers. Semantic tokens are the swap point; primitives never move.
产品是否需要多种视觉模式、品牌或租户?
|-- 否 --> 是否有组件需要偏离系统默认值?
|   |-- 否  --> 两层:基础+语义
|   |-- 是 --> 两层,仅为偏离的组件添加组件令牌
|-- 是 --> 三层。语义令牌是切换点;基础令牌永不移动。

Which tier does this value belong to?

该值属于哪个层级?

Is it a literal (a hex, a rem, a ms)?
|-- YES --> Primitive. It gets a position-in-a-ramp name, not a meaning name.
|-- NO  --> Does more than one component share this decision?
    |-- YES --> Semantic. Name the role.
    |-- NO  --> Is the component genuinely diverging, or just the only current user?
        |-- Diverging   --> Component token
        |-- Only user   --> Semantic. One user today is still a shared decision tomorrow.
它是字面量吗(十六进制、rem、ms)?
|-- 是 --> 基础令牌。命名应体现其在渐变中的位置,而非含义。
|-- 否  --> 是否有多个组件共享此值?
    |-- 是 --> 语义令牌。命名应体现其作用。
    |-- 否  --> 组件是真正偏离,还是仅为当前唯一使用者?
        |-- 真正偏离   --> 组件令牌
        |-- 唯一使用者   --> 语义令牌。当前唯一使用者仍代表未来可能的共享决策。

Does this need a build pipeline?

是否需要构建流水线?

Do tokens need to exist in more than one language or platform?
|-- YES --> Pipeline. Generate every output; hand-maintaining two copies guarantees drift.
|-- NO  --> Are tokens consumed by anything outside this repository?
    |-- YES --> Pipeline. The published artifact needs a stable, versioned shape.
    |-- NO  --> Hand-written CSS custom properties. Add the pipeline at the second consumer.
令牌是否需要支持多种语言或平台?
|-- 是 --> 流水线。生成所有输出;手动维护两份内容必然会出现差异。
|-- 否  --> 令牌是否被当前仓库外的内容使用?
    |-- 是 --> 流水线。发布的产物需要稳定、带版本的结构。
    |-- 否  --> 手写CSS自定义属性。当出现第二个使用者时再添加流水线。

Where does this token get declared?

该令牌应在哪里声明?

Is it a primitive?
|-- YES --> :root, once, never reassigned
|-- NO  --> Does it change per mode, density, or tenant?
    |-- YES --> :root for the default, reassigned on the scope selector
    |-- NO  --> Is it local to one component?
        |-- YES --> On the component's own selector
        |-- NO  --> :root alongside the other semantics
</decision_framework>

<integration>
它是基础令牌吗?
|-- 是 --> :root,声明一次,绝不重新赋值
|-- 否  --> 它是否随模式、密度或租户变化?
    |-- 是 --> 默认值在:root中,在作用域选择器上重新赋值
    |-- 否  --> 它是否属于单个组件?
        |-- 是 --> 在组件自身的选择器上声明
        |-- 否  --> 与其他语义令牌一起放在:root中
</decision_framework>

<integration>

Integration Notes

集成说明

Consuming surfaces: the token set is a contract, and every consumer reads the same custom properties. A styling approach that can read
var(--token)
-- stylesheets, scoped component styles, inline styles, a utility framework's theme block -- needs no adapter. One that cannot read custom properties at runtime needs a generated artifact from the pipeline instead, which is exactly what a second platform target is for.
Publishing tokens: when tokens ship outside the repository that owns them, add the namespace segment to every name and version the generated artifact, not the source. Consumers pin the artifact; the source is free to reorganise as long as the generated names hold.
What the token layer does NOT own: how a mode is selected and applied at runtime, how utility classes are written, and how component props map to styles. Those consume tokens; they do not define them.
</integration>
<red_flags>
使用场景: 令牌集是契约,所有使用者读取相同的自定义属性。任何能读取
var(--token)
的样式方案——样式表、组件作用域样式、内联样式、工具类框架的主题块——都无需适配器。无法在运行时读取自定义属性的方案,则需要从流水线生成产物,这正是多平台目标的用途。
发布令牌: 当令牌在拥有它的仓库外发布时,为每个名称添加命名空间段,并为生成的产物添加版本,而非为源文件添加版本。使用者固定产物版本;源文件可自由重组,只要生成的名称保持不变。
令牌层不负责: 运行时模式的选择与应用、工具类的编写、组件属性到样式的映射。这些内容使用令牌,但不定义令牌。
</integration>
<red_flags>

RED FLAGS

风险警示

High Priority Issues:
  • A component reading a primitive (
    background: var(--palette-slate-100)
    ) -- the indirection layer is bypassed, so a rebrand has to touch every component instead of one semantic file. Alias a semantic token and use that.
  • Hex or px literals in component CSS -- an untracked token. It will not move with the system, will not respond to a mode change, and will not appear in any audit.
  • Mode baked into the token name (
    --color-dark-bg
    ,
    --text-light-muted
    ) -- the name is only true in one mode, so every additional mode forks the name and every consumer needs a conditional. Reassign one role-named token per scope instead.
  • Channel triplets as token values (
    --color-surface: 0 0% 100%
    ) -- not a color, so
    color-mix()
    ,
    oklch()
    interpolation, and every opacity modifier produce invalid CSS that silently drops the declaration. Store complete color values.
  • TypeScript constants and CSS variables maintained side by side -- they drift on the first change made in only one of them, and nothing fails loudly. Generate both from one source or keep only one.
  • An inverted or cyclic graph -- a primitive referencing a semantic token, or two tokens aliasing each other. Conforming pipelines error on cycles; hand-written CSS just resolves to nothing at runtime.
Medium Priority Issues:
  • Token-per-usage sprawl (
    --button-primary-hover-icon-margin-left
    ) -- a token per property per element is a stylesheet with extra steps. Tokens encode shared decisions; a value used in exactly one declaration and shared with nothing is just a value.
  • A component tier that mirrors the component tree -- component tokens are exceptions, not coverage. A pass-through token that overrides nothing anywhere is indirection with no payoff.
  • Fallbacks in consumers (
    var(--color-surface, #fff)
    ) -- hides a missing token behind a plausible value, so the bug ships. Let it be visibly broken.
  • Mixed scale conventions in one namespace (
    --space-sm
    beside
    --space-400
    ) -- callers cannot predict which convention a given name uses, so every lookup is a guess.
  • Semantic names that describe appearance (
    --color-blue-action
    ) -- half a semantic token. The role survives a rebrand; the colour name does not.
  • Skipping
    outputReferences
    in a generated CSS platform
    -- every semantic token flattens to a literal, so the aliasing that made the tier system worth building disappears from the output.
Common Mistakes:
  • Adding the component tier preemptively rather than at the first genuine divergence
  • Regenerating a primitive ramp and expecting semantics to follow, when semantics hold literals instead of aliases
  • Declaring
    $type
    on every token when it hoists from the closest ancestor group that declares it
  • Naming a state segment somewhere other than last, so
    -hover-danger
    and
    -danger-hover
    both exist
  • Treating density as a second spacing scale instead of a multiplier applied where primitives become semantics
Gotchas & Edge Cases:
  • @theme
    cannot be nested
    under a selector or media query -- it is top-level only. Mode-swappable values therefore live in
    :root
    /
    [data-theme]
    , and only the alias goes in
    @theme inline
    . Omitting
    inline
    makes the generated utility resolve the reference at the wrong scope, and the mode swap fails silently rather than erroring.
  • @property
    requires both
    syntax
    and
    inherits
    ; if either is missing the whole rule is invalid and ignored.
    initial-value
    is required for every
    syntax
    except
    *
    , and must be computationally independent --
    10px
    is valid,
    3em
    is not.
  • An unregistered custom property cannot be transitioned or animated. It is treated as an untyped token and jumps between values. Registration is the only way to interpolate one.
  • CSS.registerProperty()
    takes precedence over
    @property
    for the same name, so a stray JS registration silently overrides the stylesheet.
  • DTCG
    {alias}
    syntax always resolves to a complete
    $value
    .
    Reaching into part of a composite token needs JSON Pointer (
    #/color/blue/$value
    ). Referencing a group rather than a token is an error in current tooling.
  • DTCG (
    $value
    ) and the original format (
    value
    ) cannot be combined in one instance
    -- a half-migrated token tree fails in ways that look like missing tokens.
  • $type
    inheritance flows from the closest ancestor group that declares it
    , and a group must never contain both
    $value
    and child tokens -- that shape is neither a group nor a token.
  • Token names cannot start with
    $
    or contain
    {
    ,
    }
    , or
    .
    -- the period is reserved for alias path construction, so a name containing one produces an unresolvable reference.
  • Custom properties inherit by default, so a component token declared on a component selector leaks into every descendant including slotted children.
    inherits: false
    via
    @property
    is the fix when that matters.
  • calc()
    chains through several tiers resolve at use time, not at declaration time
    -- a unit error three aliases up surfaces as a silently dropped declaration at the consumer, with no indication of which link broke.
</red_flags>

<critical_reminders>
高优先级问题:
  • 组件直接读取基础令牌
    background: var(--palette-slate-100)
    )——绕过了间接层,品牌重塑时必须修改所有组件,而非仅修改语义文件。应创建语义令牌别名并使用它。
  • 组件CSS中的十六进制或像素字面量——未被跟踪的令牌。它不会随系统变更,不会响应模式切换,也不会出现在任何审计中。
  • 令牌名称中包含模式
    --color-dark-bg
    --text-light-muted
    )——名称仅在一种模式下有效,新增模式时必须创建新名称,且所有使用者都需要添加条件判断。应按作用域重新赋值单一作用命名的令牌。
  • 令牌值为通道三元组
    --color-surface: 0 0% 100%
    )——这不是完整颜色,
    color-mix()
    oklch()
    插值和所有透明度修饰符都会生成无效CSS,导致声明被静默丢弃。应存储完整颜色值。
  • 手动维护TypeScript常量与CSS变量——首次仅修改其中一份内容时就会出现差异,且不会有明显错误。应从单一源生成两者,或仅保留其一。
  • 反向或循环依赖图——基础令牌引用语义令牌,或两个令牌互相别名。符合规范的流水线会对循环依赖报错;手写CSS则会在运行时解析为空。
中优先级问题:
  • 令牌过度膨胀
    --button-primary-hover-icon-margin-left
    )——为每个元素的每个属性都创建令牌,相当于多此一举的样式表。令牌编码共享决策;仅在一处使用且无共享的价值只是普通值。
  • 组件层级镜像组件树——组件令牌是例外,而非全覆盖。无任何覆盖的传递令牌毫无意义。
  • 使用者中添加回退值
    var(--color-surface, #fff)
    )——用看似合理的值掩盖缺失的令牌,导致错误被发布。应让错误可见。
  • 同一命名空间内混合比例约定
    --space-sm
    --space-400
    并存)——调用者无法预测名称使用的约定,每次查找都是猜测。
  • 描述外观的语义名称
    --color-blue-action
    )——半语义令牌。作用能在品牌重塑后存活,但颜色名称不能。
  • 生成CSS平台时跳过
    outputReferences
    ——所有语义令牌都被展平为字面量,层级系统的别名优势在输出中消失。
常见错误:
  • 提前添加组件层级,而非在首次真正偏离时添加
  • 重新生成基础渐变,却期望语义令牌自动更新,而实际上语义令牌存储的是字面量而非别名
  • 在每个令牌上声明
    $type
    ,而实际上它会从最近的祖先组继承
  • 状态段未放在最后,导致
    -hover-danger
    -danger-hover
    并存
  • 将密度视为第二个间距比例,而非应用在基础转语义环节的乘数
陷阱与边缘情况:
  • @theme
    不能嵌套
    在选择器或媒体查询下——它只能是顶级的。因此支持模式切换的值放在
    :root
    /
    [data-theme]
    中,仅别名放入
    @theme inline
    。省略
    inline
    会导致生成的工具类在错误的作用域解析引用,模式切换静默失败而非报错。
  • @property
    需要同时指定
    syntax
    inherits
    ;若缺少其中一个,整个规则无效且被忽略。除
    *
    外的所有
    syntax
    都需要
    initial-value
    ,且必须是计算独立的值——
    10px
    有效,
    3em
    无效。
  • 未注册的自定义属性无法过渡或动画。它会被视为无类型令牌,在值之间跳跃。注册是实现插值的唯一方式。
  • CSS.registerProperty()
    会覆盖样式表中同名的
    @property
    ——零散的JS注册会静默覆盖样式表中的声明。
  • DTCG
    {alias}
    语法始终解析为完整的
    $value
    。访问复合令牌的部分内容需要JSON指针(
    #/color/blue/$value
    )。引用组而非令牌在当前工具中是错误的。
  • DTCG(
    $value
    )与原始格式(
    value
    )不能在同一实例中混合
    ——半迁移的令牌树会出现类似令牌缺失的错误。
  • $type
    从最近的声明它的祖先组继承
    ,且组不能同时包含
    $value
    和子令牌——这种结构既不是组也不是令牌。
  • 令牌名称不能以
    $
    开头,也不能包含
    {
    }
    .
    ——句点用于别名路径构建,包含句点的名称会生成无法解析的引用。
  • 自定义属性默认继承,因此在组件选择器上声明的组件令牌会泄漏到所有后代,包括插槽子元素。当需要防止泄漏时,通过
    @property
    设置
    inherits: false
    是解决方案。
  • 跨多层级的
    calc()
    链在使用时解析,而非声明时
    ——三层别名之上的单位错误会在使用者处表现为静默丢弃的声明,且无法指示哪个环节出错。
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

关键提醒

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST route every component style through a semantic or component token - NEVER let component CSS read a primitive token or a raw hex/px literal)
(You MUST name tokens for their role, NEVER for a mode or a literal value -
--color-surface-raised
, never
--color-dark-bg
or
--color-gray-900-bg
)
(You MUST store complete color values in tokens - NEVER bare channel triplets like
0 0% 100%
, which break
color-mix()
opacity math and
oklch()
interpolation)
(You MUST keep the dependency flow one-way: primitive -> semantic -> component - NEVER let a primitive reference a semantic token, and NEVER create a cycle)
(You MUST generate every additional output from one source when tokens exist in more than one language - NEVER hand-maintain a TypeScript constant beside a CSS variable)
Failure to follow these rules produces a token set that cannot be rebranded, cannot gain a mode without forking every name, and drifts silently between languages.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入顺序、
import type
、命名常量)
(必须通过语义或组件令牌管理所有组件样式——绝对不能让组件CSS直接读取基础令牌或原始十六进制/像素字面量)
(必须根据令牌的作用命名,绝对不能以模式或字面量命名——例如
--color-surface-raised
,而非
--color-dark-bg
--color-gray-900-bg
(必须在令牌中存储完整颜色值——绝对不能仅存储
0 0% 100%
这类通道三元组,否则会破坏
color-mix()
透明度计算和
oklch()
插值)
(必须保持依赖流向单向:基础→语义→组件——绝对不能让基础令牌引用语义令牌,也不能创建循环依赖)
(当令牌需支持多种语言/平台时,必须从单一源生成所有额外输出——绝对不能手动维护TypeScript常量与CSS变量两份内容)
不遵循这些规则会导致令牌集无法进行品牌重塑、新增模式时必须修改所有名称,且跨语言内容会出现静默差异。
</critical_reminders>