mcpField
T
tokens

tokens

github.com/dawitlabs设计与 UI744 次安装24 周前更新
代码仓库

关于 tokens

tokens 是用于审计硬编码设计值并建立分层设计令牌系统的编码 Agent 技能,涵盖令牌映射、替换、视觉检查与结果报告。

主要功能

  1. 01审计硬编码颜色、间距和字体
  2. 02组织基础、语义及组件令牌
  3. 03将代码中的值替换为令牌
  4. 04对比修改前后的截图
  5. 05报告令牌清单与未解决值

适用场景

  1. 01统一代码库中分散的样式值
  2. 02将重复硬编码值改为命名令牌
  3. 03检查桌面端与移动端的视觉变化
  4. 04记录令牌化进度及遗留值

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dawitlabs/ui-skills --skill tokens

安装后在 Claude Code 中输入/tokens确认已加载。

附带文件SYSTEMS.md

详细介绍

/tokens — 设计令牌系统

审计代码库中所有硬编码的设计值,构建或强制实施三层令牌系统(原始值 → 语义 → 组件),并在整个代码库中用令牌替换硬编码值。

令牌架构和命名规范:SYSTEMS.md

如果项目根目录中存在 DESIGN-BRIEF.md,请在开始前阅读。设计简报中的 Color System 和 Typography 字段提供了原始令牌的确切起点——直接使用其中的十六进制颜色值和字体名称,不要从头推导。


阶段 0 — 环境设置

0a. 查找源码根目录
find . -type d -name "src" \
  -not -path "*/node_modules/*" \
  -not -path "*/.next/*" \
  -not -path "*/dist/*" \
  -not -path "*/.turbo/*" \
  | head -1

如果没有 src/:find . -type d -name "app" -not -path "*/node_modules/*" -not -path "*/.next/*" | head -1

在下文中始终将结果用作 <SRC>。

0b. 检测 CSS-in-JS
grep -r "styled-components\|@emotion\|@stitches\|vanilla-extract\|linaria" package.json packages/*/package.json apps/*/package.json 2>/dev/null | head -5

如果找到,则阶段 1 中搜索颜色和间距的命令也必须扫描 .ts 文件和模板字面量——阶段 1 的命令会在需要时包含这些内容。记录正在使用的库。

0c. 检测现有令牌系统

运行以下所有命令,并记录哪些命令有结果:

# CSS custom properties (most common)
find . -name "globals.css" -o -name "variables.css" -o -name "tokens.css" | grep -v node_modules

# Tailwind config
find . -name "tailwind.config.*" -not -path "*/node_modules/*"

# Style Dictionary / tokens.json
find . -name "tokens.json" -o -name "design-tokens.json" | grep -v node_modules
find . -type d -name "tokens" -not -path "*/node_modules/*"

# shadcn/ui pattern (CSS vars in globals.css under :root)
grep -rn ":root" . --include="*.css" -l | grep -v node_modules | head -5

# Existing token usage (CSS vars already in use)
grep -rn "var(--" "$SRC" --include="*.tsx" --include="*.css" | head -10

将结果对应到以下其中一个令牌存放位置——所有修复都在此处进行:

  • CSS 自定义属性 → globals.css 的 :root 块
  • Tailwind theme.extend → tailwind.config.ts
  • Style Dictionary → tokens/ 目录中的 JSON 文件
  • 无 → 创建包含 :root 块的 globals.css(参见 SYSTEMS.md)
0d. 记录当前令牌集

读取在 0b 中找到的令牌存放位置文件。列出每个现有令牌名称及其值。这是基准——新令牌不得与这些令牌冲突或重复。

0e. 检查/启动开发服务器(用于视觉验证)
curl -s --max-time 2 -o /dev/null -w "%{http_code}" http://localhost:3000

如果状态不是 200,启动开发命令并等待:

until curl -s --max-time 2 http://localhost:3000 > /dev/null 2>&1; do sleep 1; done && echo "ready"
0f. 编写截图脚本

写入项目根目录(需要 node_modules)。在阶段 4 中删除。

_tokens_screenshot.mjs:

import { chromium } from '@playwright/test';
import { mkdirSync } from 'fs';

const get = (f) => { const i = process.argv.indexOf(f); return i !== -1 ? process.argv[i+1] : null; };
const url    = get('--url');
const output = get('--output') ?? '/tmp/tokens-screenshot.png';
const width  = parseInt(get('--width')  ?? '1280');
const height = parseInt(get('--height') ?? '900');
const auth   = get('--auth');

if (!url) { console.error('--url required'); process.exit(1); }
mkdirSync('/tmp/tokens', { recursive: true });

const browser = await chromium.launch();
const context = await browser.newContext({
  viewport: { width, height },
  ...(auth ? { storageState: auth } : {}),
});
const page = await context.newPage();
try {
  await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
} catch {
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
}
await page.screenshot({ path: output, fullPage: true });
await browser.close();
console.log('saved:', output);

每次截图后:使用 Read 工具读取图片。

0g. 处理受身份验证保护的屏幕
ls /tmp/uiux-auth-state.json /tmp/animate-auth-state.json /tmp/copy-auth-state.json /tmp/a11y-auth-state.json 2>/dev/null | head -1

如果找到,则对所有受保护路由的截图调用传入 --auth <path>。


阶段 1 — 令牌审计

1a. 查找所有硬编码颜色
SRC=<your source root>

# Hex colors in TSX/JSX (inline styles, arbitrary Tailwind values)
echo "=== Hex colors in TSX ===" && \
grep -rn "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.tsx" --include="*.jsx" \
  | grep -v "//.*#[0-9a-fA-F]" | grep -v "\.md"

# Hex colors in CSS
echo "=== Hex colors in CSS ===" && \
grep -rn "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.css" --include="*.scss" \
  | grep -v "^.*:.*var(--"

# rgb/hsl inline values
echo "=== RGB/HSL values ===" && \
grep -rn "rgba\?\|hsla\?" "$SRC" --include="*.tsx" --include="*.css" | grep -v "var(--"

# Arbitrary Tailwind color values (hex)
echo "=== Arbitrary Tailwind colors (hex) ===" && \
grep -rn "\(bg\|text\|border\|ring\|shadow\|fill\|stroke\)-\[#" "$SRC" --include="*.tsx"

# Named Tailwind palette colors that should be semantic tokens
# e.g. bg-blue-500 should become bg-primary if blue-500 is the primary color
echo "=== Named Tailwind palette colors ===" && \
grep -rn "\(bg\|text\|border\|ring\|fill\|stroke\)-\(slate\|gray\|zinc\|neutral\|stone\|red\|orange\|amber\|yellow\|lime\|green\|emerald\|teal\|cyan\|sky\|blue\|indigo\|violet\|purple\|fuchsia\|pink\|rose\)-[0-9]\{2,3\}" "$SRC" --include="*.tsx" \
  | grep -v "hover:\|focus:\|dark:\|disabled:" | head -40
# Note: filter hover/focus/dark variants — those are less likely to be primary brand usage
# High-frequency named colors (>5 occurrences) are strong tokenization candidates

如果检测到 CSS-in-JS(styled-components / emotion): 还要运行:

# Hex colors in template literals
grep -rn "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.ts" --include="*.tsx" \
  | grep "styled\.\|css\`\|createGlobalStyle\|keyframes" | grep -v "//.*#"

# CSS property values inside template literals
grep -rn "color:\s*#\|background:\s*#\|border.*:\s*#" "$SRC" --include="*.ts" --include="*.tsx"
1b. 查找所有硬编码间距
# Off-grid pixel values in CSS (not on 4px scale)
echo "=== Off-grid spacing in CSS ===" && \
grep -rn "\b[0-9]*[13579]\b\s*px\|[0-9]\+\.\s*[0-9]\+\s*px" "$SRC" --include="*.css" \
  | grep -v "border\|outline\|shadow\|translate"

# Arbitrary Tailwind spacing values
echo "=== Arbitrary Tailwind spacing ===" && \
grep -rn "\(p\|m\|gap\|space\|w\|h\|inset\|top\|left\|right\|bottom\)-\[[0-9]" "$SRC" --include="*.tsx"

# Inline style pixel values
echo "=== Inline style px values ===" && \
grep -rn "style={{" "$SRC" --include="*.tsx" | grep "[0-9]px\|[0-9]rem" | head -20
1c. 查找所有硬编码排版值
# Font sizes in CSS not from scale
echo "=== Hardcoded font sizes ===" && \
grep -rn "font-size:" "$SRC" --include="*.css" | grep -v "var(--"

# Arbitrary Tailwind font sizes
echo "=== Arbitrary Tailwind font sizes ===" && \
grep -rn "text-\[[0-9]" "$SRC" --include="*.tsx"

# Font weights not from scale
echo "=== Hardcoded font weights ===" && \
grep -rn "font-weight:\s*[0-9]" "$SRC" --include="*.css" | grep -v "var(--"

# Line heights
echo "=== Hardcoded line heights ===" && \
grep -rn "line-height:\s*[0-9]" "$SRC" --include="*.css" | grep -v "var(--"
1d. 查找所有硬编码阴影、圆角和 z-index
# Box shadows not from token
echo "=== Hardcoded shadows ===" && \
grep -rn "box-shadow:" "$SRC" --include="*.css" | grep -v "var(--" && \
grep -rn "shadow-\[" "$SRC" --include="*.tsx"

# Border radii not from token
echo "=== Hardcoded border radii ===" && \
grep -rn "border-radius:" "$SRC" --include="*.css" | grep -v "var(--" && \
grep -rn "rounded-\[[0-9]" "$SRC" --include="*.tsx"

# Magic z-index numbers
echo "=== Magic z-index ===" && \
grep -rn "z-index:\s*[0-9]\|z-\[[0-9]" "$SRC" --include="*.css" --include="*.tsx"
1e. 查找重复和不一致的值
# Same color used with different names (group by hex value to find duplicates)
grep -rh "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.tsx" --include="*.css" \
  | grep -oE "#[0-9a-fA-F]{3,8}" | sort | uniq -c | sort -rn | head -20

高频值 = 使用次数最多的硬编码颜色。这些值应优先转换为令牌。

1f. 截取每个主要屏幕的截图(变更前)

为每个屏幕分别截取桌面端和移动端的截图——间距和排版方面的令牌变更可能会破坏移动端布局,而这些布局在 1280px 下看起来正常。

node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-before-desktop.png
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-before-mobile.png --width 375 --height 812

选取 3–5 个视觉差异最明显的屏幕。**读取每张图片。**这些是变更前的基准状态。

继续之前,按类别展示完整的审计结果。


阶段 2 — 令牌映射

编写任何代码之前,先建立令牌映射——将每个硬编码值对应到建议的令牌名称。

格式:

Hardcoded value | Occurrences | Proposed token name | Layer
#3b82f6         | 23          | --color-primary      | semantic
#1d4ed8         | 8           | --color-primary-dark  | semantic
#f9fafb         | 31          | --color-surface       | semantic
13px            | 4           | --spacing-3-5         | primitive

命名规则(来自 SYSTEMS.md):

  • 原始令牌:--color-{hue}-{shade}、--space-{n}、--radius-{size}
  • 语义令牌:--color-primary、--color-surface、--color-text、--color-border
  • 组件令牌:--button-bg、--card-shadow(仅限特定组件)
  • 不要按视觉外观命名:--blue 或 --dark-gray → 使用语义名称
  • 语义名称在颜色变更后依然适用:品牌色从蓝色变为绿色时,--color-primary 仍然合适

按令牌层分组:

  1. 新增原始令牌 — 系统中尚未定义的原始值
  2. 新增语义令牌 — 映射到原始令牌、基于含义命名的别名
  3. 要使用的现有令牌 — 可映射到已定义令牌的值

请用户确认令牌映射后,再开始阶段 3。


第 3 阶段 — 递归令牌循环

按类别逐一处理:颜色 → 间距 → 排版 → 阴影 → 圆角 → z-index。 每个类别最多 3 次迭代。

3a. 定义新令牌

在替换任何用法之前,先将所有新的原始令牌和语义令牌添加到令牌主文件中。

CSS 自定义属性模式:

/* In globals.css */
:root {
  /* Primitives */
  --color-blue-500: #3b82f6;
  --color-blue-700: #1d4ed8;
  --space-1: 4px;
  --space-2: 8px;

  /* Semantic */
  --color-primary:      var(--color-blue-500);
  --color-primary-dark: var(--color-blue-700);
  --color-surface:      #f9fafb;
  --color-text:         #111827;
  --color-border:       #e5e7eb;
}

/* Dark mode — same semantic names, different values */
.dark {
  --color-primary:  var(--color-blue-400);
  --color-surface:  #111827;
  --color-text:     #f9fafb;
  --color-border:   #374151;
}

Tailwind 模式:

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        primary:  'var(--color-primary)',
        surface:  'var(--color-surface)',
        border:   'var(--color-border)',
      },
      spacing: {
        '3.5': 'var(--space-3-5)',
      },
    },
  },
}
3b. 替换组件中的硬编码值

按文件逐一处理,优先处理出现次数最多的值。对每个文件:

  1. 读取文件
  2. 将硬编码值替换为令牌
  3. 切勿更改逻辑、布局或组件结构——只替换值

CSS 替换:

/* Before */
color: #3b82f6;

/* After */
color: var(--color-primary);

Tailwind 类替换:

/* Before */
<div className="bg-[#3b82f6] text-[#111827]">

/* After */
<div className="bg-primary text-text">

内联样式替换(最后手段——优先移至 CSS/Tailwind):

/* Before */
style={{ backgroundColor: '#3b82f6' }}

/* After — move to CSS class if possible */
className="bg-primary"

/* Or if dynamic: */
style={{ backgroundColor: 'var(--color-primary)' }}
3c. 每个类别处理完后截取屏幕截图
sleep 3  # hot reload
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-after-<category>-desktop.png
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-after-<category>-mobile.png --width 375 --height 812

读取两张图片。 对照两个视口下的处理前截图进行比较。

两个尺寸下的 UI 都应当 完全一致。如果有任何变化:

  • 颜色错误 → 令牌值有误,修正令牌定义
  • 移动端布局偏移 → 间距/尺寸令牌解析出的值与硬编码值不同,修正令牌值
3d. 令牌检查清单

进入下一个类别之前:

  • 此类别中的所有值都已替换为令牌
  • 替换过程中没有引入新的硬编码值
  • 处理前后的桌面端截图在视觉上完全一致
  • 处理前后的移动端截图(375px)在视觉上完全一致——间距/排版令牌不会导致布局偏移
  • 令牌名称遵循原始 → 语义层级
  • 暗色模式代码块为所有语义令牌定义了覆盖值(如果存在暗色模式)
  • 没有两个令牌具有相同的值但语义不同(合并重复项)
  • 令牌名称不描述外观(--blue)——只描述其作用(--color-primary)

任一项未通过 → 返回 3b。第 3 次迭代后:标记问题并继续。


第 4 阶段 — 清理与报告

删除临时脚本:

rm -f _tokens_screenshot.mjs

每个类别处理完后:

✓ Colors (iteration 1) — 47 hardcoded values → 8 tokens
✓ Spacing (iteration 2) — 12 hardcoded values → 4 tokens

最终摘要表:

类别处理前的硬编码值创建的令牌已替换的值迭代次数状态
颜色............✓ / ⚠
间距............✓ / ⚠
排版............✓ / ⚠
阴影............✓ / ⚠
圆角............✓ / ⚠
Z-index............✓ / ⚠

令牌清单 — 列出所有已定义的令牌、其值及使用次数。

列出所有无法解决的硬编码值并标记,同时说明原因。


规则

  1. 替换前先定义。 任何组件引用令牌之前,令牌必须已存在于令牌目录中。
  2. 先定义原始值,再定义语义值。 先定义原始值,再定义语义别名,只有必要时才定义组件令牌。
  3. 按角色命名,绝不按外观命名。 --color-primary 可以适应品牌重塑,--color-blue 则不行。
  4. 截图是回归测试。 替换前后必须在视觉上完全一致。如果存在差异,说明令牌值不正确。
  5. 深色模式是检验语义层是否良好的标准。 如果切换 .dark 后颜色能正确变化,且无需修改组件,那么令牌就是正确的。
  6. 每种含义只用一个令牌。 两个令牌的值和含义相同 → 合并。
  7. 不要把所有内容都令牌化。 border: 1px solid 中的 1px 不需要令牌。重点关注会随主题变化或在多个位置出现的值。
  8. 每个类别最多迭代 3 次。 标记后继续处理。
  9. 绝不更改逻辑。 只修改设计值,不改条件、组件结构或功能。

令牌架构、命名约定和完整代码示例,请参阅 SYSTEMS.md。

评论

支持 Markdown