Raft 设计系统

基础

Token

每组 token 及其在两种模式下的值,token 名怎么构成,tokens.json 放在哪里、foundation.css 如何由它生成,以及如何迁移弃用的名称。

token 是一个有名字的值:一种颜色、一种阴影、一个尺寸。组件和产品只用名字,从不直接用值,所以一个值只需在一处修改,所有界面在两种模式下都会跟着变。本页是所有 token 的参考。在 token 之间如何选择的规则,归属于各组 token 所属的基础页面,下面每一节都附有链接。

原则

  • 只有一个来源。 tokens.json 里的每个 token 只在那里写一次。foundation.css 由它生成,其他任何地方都不定义 token 的值。
  • 名字说明角色,不说明颜色。 写 attention,不写粉色;写 layer-canvas,不写白色。值变了,token 的名字不变;深色模式的值也放在同一个名字下。
  • 先用角色 token,再用调色板。 只有在没有合适角色时才用 palette-*;见颜色。
  • 一个值用到第三次就变成 token。 只用一次的值可以内联,写成一个墨色阶或一个 oklch() 字面量;用到第三次时,添加一个 token。

token 放在哪里

文件内容
packages/ui/src/tokens.json颜色、阴影、焦点环和尺寸类 token 的来源,采用 Design Tokens Community Group 格式(2025.10)。以 @botiverse/raft-ui/tokens.json 发布,供 CSS 以外的工具使用。
packages/ui/src/foundation.css由 tokens.json 生成:每个 token 都是一个 CSS 自定义属性,分别用于浅色模式、深色模式和跟随系统设置。以 @botiverse/raft-ui/foundation.css 发布,供纯 CSS 使用。
packages/ui/src/styles.cssTailwind 入口。它引入 foundation.css,把 token 映射成工具类(bg-layer-canvas、text-foreground-muted、shadow-float),并定义只作为 Tailwind 主题值存在的 token:圆角、文字样式和 ease-slide。
packages/ui/src/fonts.css字体族:Space Grotesk、Space Mono 和 CJK 后备字体(默认简体中文;繁体中文、日文和韩文在各自的 lang 下)。

使用 Tailwind 的应用引入 @botiverse/raft-ui/styles.css。不用 Tailwind 的页面引入 @botiverse/raft-ui/foundation.css,读取自定义属性:var(--layer-canvas)。

foundation.css 如何生成

packages/ui/scripts/build-tokens.mjs 读取 tokens.json,写出 foundation.css。不要手动编辑 foundation.css。

sh
vp run @botiverse/raft-ui#build:tokens          # write src/foundation.css
node packages/ui/scripts/build-tokens.mjs --check   # exit 1 if it is out of date
  • 在 tokens.json 里改一个值,运行 build:tokens,然后两个文件一起提交。foundation.css 与 tokens.json 不一致时,测试(foundation.generated.test.ts)会失败。
  • token 的路径就是它的属性名:{layer.canvas} 对应 --layer-canvas。$root 表示分组本身:{foreground.$root} 对应 --foreground。引用("{palette.cyan.800}")会变成 var(--palette-cyan-800)。
  • 这个格式没有表示模式的字段,所以 tokens.json 加了一个扩展 build.botiverse.raft:
字段含义
modes.dark深色模式的值。没有这一项的 token 在两种模式下值相同
desktop桌面端的值:从 768px 起(桌面布局从这里开始),并使用鼠标或触控板(间距);此时 token 自身的值是手机和触屏的值。用于 field-font-size 这类尺寸 token
scope: "root"不随模式变化的值,比如调色板:只在 :root 上声明一次
scope: "every-mode"随模式变化的 token 的别名:在每个模式的作用域里再声明一次,这样它解析的是该模式下的值
css: true保留为 CSS 表达式的值,格式中没有对应的类型:color-mix()、多层阴影
static: true不随模式变化的值的弃用别名

生成的文件依次包含:

  1. 调色板,在 :where(:root) 上;
  2. 浅色模式,在 :where(:root) 和 .light 上;
  3. 深色模式,在 .dark 上;
  4. 再写一遍深色模式,放在 @media (prefers-color-scheme: dark) 下,针对没有 .light 或 .dark 的根节点,这样在应用设置 class 之前页面的颜色就是对的;
  5. 在每个模式中都声明的别名,然后是弃用的名称;
  6. 桌面端的值,放在 @media (width >= 48rem) and (pointer: fine) 下(也是 styles.css 中桌面字号用的查询),选择器与它们所替换的值相同。

深色页面里的 .light 元素是一个浅色孤岛,用于预览或传送门(portal);styles.css 中的 dark: 变体跟随最近的模式 class。见深色模式。

token 命名规则

名字由几个部分组成,所以即使是没见过的名字,你也能看出它是做什么的。

类别模式示例
中性角色{role}(-{purpose})(-{intensity})layer-canvas、foreground-hint、line-seam、fill-medium、foreground-on-hud
颜色角色{family}(-{slot})brand、brand-soft、danger-strong、info-muted
墨色阶ink-{alpha}ink-8、ink-40
调色板palette-{color}-{step}palette-yellow-400、palette-lavender-800
状态task-status-{status}(-soft|-strong)、presence-{state}task-status-in-review-soft、presence-busy
贴纸sticker-{part}sticker-ink、sticker-edge、sticker-medium
阴影elevation-{level}、focus-ringelevation-float、elevation-sticker
尺寸{part}-{property}field-font-size、card-title-line-height

中性角色。 角色是以下四种之一:

  • foreground-*:文字和图标;
  • layer-*:表面,按叠放顺序排列(外框、侧边栏、画布、卡片、弹出层);
  • line-*:线;
  • fill-*:铺在表面里或表面上的填充。

用途说明 token 用在哪里:frame、sidebar、canvas、card、popover、code、hud、on-frame、on-hud、link、field。强度从不带后缀的名字往下到 -muted,文字再往下是 -hint;线则从 -hairline 经 -seam 到 -strong。

颜色角色。 色系是 brand、attention、info、success、warning、danger 或 inactive 之一。槽位说明是哪个色阶:

槽位含义
不带后缀(info)实色:贴纸的填充
-strong深色阶,可作为文字阅读
-muted较浓的淡色
-soft很淡的淡色,不带边框;见柔和淡色
-foreground实色上的文字,仅限有自己文字色的色系
-hover、-active实色填充在悬停和按下时用的一对值。一个色系要么两个都有,要么都没有
-50 到 -950带编号的色阶。只有 brand 和 attention 有

不透明度。 除了两个遮罩 layer-backdrop 和 layer-backdrop-nested,所有 layer token 都是不透明的。fill-muted 和 fill-strong 是不透明的。fill-subtle、fill-medium、fill-frame-medium 和 sticker-medium 是半透明的:它们是悬停层级,叠在控件或行上面(悬停浮层)。

工具类。 Tailwind 工具类由属性前缀加 token 名构成:bg-layer-card、text-foreground-muted、border-line-strong。阴影另有命名:elevation-float 对应 shadow-float,elevation-sticker 对应 shadow-sticker,focus-ring 对应 shadow-focus。带编号的调色板色阶只有自定义属性,没有工具类;不带编号的 bg-palette-yellow 及同类是例外。

token 分组

下面每个区块都由 tokens.json 生成,显示浅色和深色两种值。

调色板

角色所指向的原始颜色。直接使用它们的规则见颜色。

Token浅色深色
palette-yellow-50
#fff9ed
#fff9ed
palette-yellow-100
#fff6e3
#fff6e3
palette-yellow-200
#ffe9b9
#ffe9b9
palette-yellow-300
#ffdf91
#ffdf91
palette-yellow-400
#ffd441
#ffd441
palette-yellow-500
#d3ad03
#d3ad03
palette-yellow-600
#a78802
#a78802
palette-yellow-700
#7a6300
#7a6300
palette-yellow-800
#534300
#534300
palette-yellow-900
#2d2300
#2d2300
palette-yellow-950
#1c1500
#1c1500
palette-lavender-50
#f7f6fc
#f7f6fc
palette-lavender-100
#efecf9
#efecf9
palette-lavender-200
#dcd7f2
#dcd7f2
palette-lavender-300
#ccc4ec
#ccc4ec
palette-lavender-400
#bbafe6
#bbafe6
palette-lavender-500
#9d8ada
#9d8ada
palette-lavender-600
#7f62cb
#7f62cb
palette-lavender-700
#6341b0
#6341b0
palette-lavender-800
#442b7c
#442b7c
palette-lavender-900
#251548
#251548
palette-lavender-950
#170c31
#170c31
palette-pink-50
#fff0f4
#fff0f4
palette-pink-100
#ffe1e8
#ffe1e8
palette-pink-200
#fec1d2
#fec1d2
palette-pink-300
#fea0bc
#fea0bc
palette-pink-400
#fe7da8
#fe7da8
palette-pink-500
#fe2f8b
#fe2f8b
palette-pink-600
#d4086f
#d4086f
palette-pink-700
#9f0551
#9f0551
palette-pink-800
#700238
#700238
palette-pink-900
#42011e
#42011e
palette-pink-950
#2f0014
#2f0014
palette-cyan-50
#ecf9ff
#ecf9ff
palette-cyan-100
#d7f2fe
#d7f2fe
palette-cyan-200
#a9e6fe
#a9e6fe
palette-cyan-300
#68dafd
#68dafd
palette-cyan-400
#28ccf3
#28ccf3
palette-cyan-500
#1ea6c6
#1ea6c6
palette-cyan-600
#15849f
#15849f
palette-cyan-700
#0e6276
#0e6276
palette-cyan-800
#06414f
#06414f
palette-cyan-900
#02252f
#02252f
palette-cyan-950
#01161c
#01161c
palette-orange-50
#fef5f1
#fef5f1
palette-orange-100
#fde6de
#fde6de
palette-orange-200
#fbd1c0
#fbd1c0
palette-orange-300
#f9b899
#f9b899
palette-orange-400
#f8a16f
#f8a16f
palette-orange-500
#dd7e34
#dd7e34
palette-orange-600
#ad6127
#ad6127
palette-orange-700
#83481a
#83481a
palette-orange-800
#5b310f
#5b310f
palette-orange-900
#331905
#331905
palette-orange-950
#220e02
#220e02
palette-lime-50
#f1fde9
#f1fde9
palette-lime-100
#dcfac1
#dcfac1
palette-lime-200
#c0f588
#c0f588
palette-lime-300
#b4e67f
#b4e67f
palette-lime-400
#a9d877
#a9d877
palette-lime-500
#8ab261
#8ab261
palette-lime-600
#6b8b4a
#6b8b4a
palette-lime-700
#506836
#506836
palette-lime-800
#344522
#344522
palette-lime-900
#1c2711
#1c2711
palette-lime-950
#101808
#101808
palette-red-50
#fef1f0
#fef1f0
palette-red-100
#fddedd
#fddedd
palette-red-200
#fbbdb9
#fbbdb9
palette-red-300
#fa9991
#fa9991
palette-red-400
#f97264
#f97264
palette-red-500
#eb4423
#eb4423
palette-red-600
#bb3419
#bb3419
palette-red-700
#8e2510
#8e2510
palette-red-800
#621708
#621708
palette-red-900
#3e0b03
#3e0b03
palette-red-950
#280501
#280501
palette-stone-50
#f7f6f5
#f7f6f5
palette-stone-100
#efedeb
#efedeb
palette-stone-200
#e0dcd7
#e0dcd7
palette-stone-300
#d2cbc2
#d2cbc2
palette-stone-400
#c0b9b1
#c0b9b1
palette-stone-500
#9d9891
#9d9891
palette-stone-600
#7b7671
#7b7671
palette-stone-700
#5d5955
#5d5955
palette-stone-800
#3e3b38
#3e3b38
palette-stone-900
#23211f
#23211f
palette-stone-950
#141312
#141312
palette-cream-50
#fffdf9
#fffdf9
palette-cream-100
#fffcf5
#fffcf5
palette-cream-200
#fffaef
#fffaef
palette-cream-300
#f9eccf
#f9eccf
palette-cream-400
#ebd38f
#ebd38f
palette-cream-500
#d2b100
#d2b100
palette-cream-600
#a78c00
#a78c00
palette-cream-700
#7c6800
#7c6800
palette-cream-800
#554500
#554500
palette-cream-900
#312700
#312700
palette-cream-950
#191300
#191300

文字和图标

foreground 及其各层级,以及外框和 HUD 上的文字颜色。用法和对比度:颜色、字体排印。

Token浅色深色
foreground
#141111
#e5e5e2
foreground-muted
#141111 / 68%
#c4c4c1
foreground-hint
#141111 / 62%
#9e9e9b
foreground-disabled
#141111 / 30%
#636360
foreground-inverse
#ffffff
#1b1b19
foreground-on-hud
#f3f3f5
#e5e5e2
foreground-on-hud-mutedSecondary text on the HUD: the shortcut in a tooltip, the detail line of a toast.
#f3f3f5 / 70%
#c4c4c1foreground-muted
foreground-on-frame
#141111sticker-ink
#e5e5e2
foreground-on-frame-muted
#564919
#9e9e9b
foreground-on-frame-active
#141111sticker-ink
#141111sticker-ink
foreground-link
#1447e6
#8ec5ff
foreground-active
#141111foreground
#e5e5e2foreground
foreground-hover
#141111foreground
#e5e5e2foreground

表面

用法:颜色。

Token浅色深色
layer-canvas
#ffffff
#1b1b19
layer-sidebar
#fffaef
#0d0d0b
layer-popover
#ffffff
#292927
layer-backdrop
#000000 / 65%
#000000 / 60%
layer-backdrop-nested
#000000 / 40%
#000000 / 40%
layer-card
#ffffff
#242422
layer-hud
#252429
#292927
layer-frame
#ffd440
#0d0d0b
layer-frame-active
#ffffff
#ffd440
layer-code
#ffffff
#0a0c10

填充

用法:颜色和悬停浮层。

Token浅色深色
fill-muted
#f3f3f3
#32322f
fill-strong
#eaeaea
#3a3a37
fill-subtleHover tier subtle (light): hover on large things people read, a row, a message, a readable card, and a row whose menu is open. About 2.5 to 3 OKLab dE in both modes, on paper, the sidebar and cards alike, under every selection tint, so text keeps its contrast.
#141111 / 4%
#fafaf7 / 3%
fill-mediumHover tier medium: hover on small controls (buttons, icon buttons, chips, tabs, segment items) and press on everything, on paper-coloured surfaces. About 5.5 to 6.5 OKLab dE; dark is lighter than light because the same ink alpha shows more on a dark surface.
#141111 / 8%
#fafaf7 / 7%
fill-frame-mediumHover tier medium on the frame (rail items, on-frame buttons, the workspace button): hover and press. Its own alphas because ink shows differently on the yellow and on the near-black frame: 10% on the yellow and 6% on the dark frame land on the medium step, about 6 OKLab dE.
#141111 / 10%
#fafaf7 / 6%

线

用法和宽度:颜色、形状。

Token浅色深色
line-strong
#141111
#e5e5e2
line-seam
#141111 / 45%
#fafaf7 / 26%
line-hairline
#141111 / 15%
#fafaf7 / 10%
line-frame-active
#141111sticker-ink
#141111sticker-ink
line-fieldThe frame of a field and the composer: the ink and width of line-strong, drawn 1.5px.
#141111line-strong
#e5e5e2line-strong
line-field-hover
#141111line-strong
#e5e5e2line-strong

墨色阶

半透明的墨色阶梯,线、次要文字和浮层都由它构成。请用角色 token;只有一次性的淡色铺底才用墨色阶。

Token浅色深色
ink-2
#141111 / 2%
#fafaf7 / 2%
ink-4
#141111 / 4%
#fafaf7 / 4%
ink-6
#141111 / 6%
#fafaf7 / 6%
ink-8
#141111 / 8%
#fafaf7 / 8%
ink-10
#141111 / 10%
#fafaf7 / 10%
ink-16
#141111 / 16%
#fafaf7 / 16%
ink-20
#141111 / 20%
#fafaf7 / 20%
ink-30
#141111 / 30%
#fafaf7 / 30%
ink-40
#141111 / 40%
#fafaf7 / 40%
ink
#141111
#fafaf7

颜色角色

每种颜色的含义:颜色的含义。

Token浅色深色
brand-50
#fff9edpalette-yellow-50
#fff9edpalette-yellow-50
brand-100
#fff6e3palette-yellow-100
#fff6e3palette-yellow-100
brand-200
#ffe9b9palette-yellow-200
#ffe9b9palette-yellow-200
brand-300
#ffdf91palette-yellow-300
#ffdf91palette-yellow-300
brand-400
#ffd440
#ffd440
brand-500
#d3ad03palette-yellow-500
#d3ad03palette-yellow-500
brand-600
#a78802palette-yellow-600
#a78802palette-yellow-600
brand-700
#7a6300palette-yellow-700
#7a6300palette-yellow-700
brand-800
#534300palette-yellow-800
#534300palette-yellow-800
brand-900
#2d2300palette-yellow-900
#2d2300palette-yellow-900
brand-950
#1c1500palette-yellow-950
#1c1500palette-yellow-950
brand-strong
#564919
#ffd440
brand-soft
#fff0bd
#876f1f / 20%
brand-edge
#141111sticker-ink
#141111sticker-ink
brand-glow
#ffd440
#ffd440
brand-hoverHovering or pressing a sticker lays sticker-medium (10% ink) over its colour.
color-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))
color-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))
brand-active
color-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))brand-hover
color-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))brand-hover
Token浅色深色
attention-50
#fff0f4palette-pink-50
#fff0f4palette-pink-50
attention-100
#ffe1e8palette-pink-100
#ffe1e8palette-pink-100
attention-200
#fec1d2palette-pink-200
#fec1d2palette-pink-200
attention-300
#fea0bcpalette-pink-300
#fea0bcpalette-pink-300
attention-400
#ff99c3
#ff99c3
attention-500
#fe7da8palette-pink-400
#fe7da8palette-pink-400
attention-600
#d4086fpalette-pink-600
#d4086fpalette-pink-600
attention-700
#9f0551palette-pink-700
#9f0551palette-pink-700
attention-800
#700238palette-pink-800
#700238palette-pink-800
attention-900
#42011epalette-pink-900
#42011epalette-pink-900
attention-950
#2f0014palette-pink-950
#2f0014palette-pink-950
attention-strong
#c00064
#ffacc7
attention-soft
#ffd6e2
#d0457c / 16%
attention-hover
color-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))
color-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))
attention-active
color-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))attention-hover
color-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))attention-hover
Token浅色深色
infoEvery solid is a sticker: ink type on the colour in both modes.
#27ccf3
#27ccf3
info-strong
#06414fpalette-cyan-800
#6ccdea
info-muted
#a9e6fepalette-cyan-200
#007592 / 28%
info-soft
#d7f2fe
#005f7a / 18%
info-hover
color-mix(in srgb, var(--info) 90%, var(--sticker-ink))
color-mix(in srgb, var(--info) 90%, var(--sticker-ink))
info-active
color-mix(in srgb, var(--info) 90%, var(--sticker-ink))info-hover
color-mix(in srgb, var(--info) 90%, var(--sticker-ink))info-hover
Token浅色深色
success
#a9d877
#a9d877
success-foreground
#141111sticker-ink
#141111sticker-ink
success-strong
#344522palette-lime-800
#b7e18c
success-muted
#c0f588palette-lime-200
#0c7d43 / 28%
success-soft
#dcfac1
#167641 / 18%
Token浅色深色
warning
#f8a16f
#f8a16f
warning-foreground
#141111sticker-ink
#141111sticker-ink
warning-strong
#5b310fpalette-orange-800
#ffaf76
warning-muted
#fbd1c0palette-orange-200
#ac571d / 28%
warning-soft
#fde6de
#9e511e / 18%
warning-hover
color-mix(in srgb, var(--warning) 90%, var(--sticker-ink))
color-mix(in srgb, var(--warning) 90%, var(--sticker-ink))
warning-active
color-mix(in srgb, var(--warning) 90%, var(--sticker-ink))warning-hover
color-mix(in srgb, var(--warning) 90%, var(--sticker-ink))warning-hover
Token浅色深色
dangerdanger = the destructive sticker (red fill, ink type); danger-strong is the error ink for invalid fields and destructive menu items.
#f97264
#f97264
danger-foreground
#141111sticker-ink
#141111sticker-ink
danger-strong
#b5321a
#f8a49d
danger-muted
#fbbdb9palette-red-200
#d33c30 / 30%
danger-soft
#fddedd
#c93126 / 18%
danger-hover
color-mix(in srgb, var(--danger) 90%, var(--sticker-ink))
color-mix(in srgb, var(--danger) 90%, var(--sticker-ink))
danger-active
color-mix(in srgb, var(--danger) 90%, var(--sticker-ink))danger-hover
color-mix(in srgb, var(--danger) 90%, var(--sticker-ink))danger-hover
Token浅色深色
inactive
#c0b9b1
#c0b9b1
inactive-foreground
#141111sticker-ink
#141111sticker-ink
inactive-soft
#e0dcd7
#32322f

状态

任务状态和在线状态灯:状态。

Token浅色深色
task-status-todo
#f8a16f
#ff6900
task-status-todo-soft
#fde6de
#9e511e / 18%
task-status-todo-strong
#5b310fpalette-orange-800
#ffaf76
task-status-in-progress
#27ccf3
#27ccf3
task-status-in-progress-soft
#d7f2fe
#005f7a / 18%
task-status-in-progress-strong
#06414fpalette-cyan-800
#6ccdea
task-status-in-review
#bbafe6
#9d8ada
task-status-in-review-soft
#efecf9
#6853a0 / 24%
task-status-in-review-strong
#442b7cpalette-lavender-800
#ccc4ecpalette-lavender-300
task-status-done
#a9d877
#20c16b
task-status-done-soft
#dcfac1
#167641 / 18%
task-status-done-strong
#344522palette-lime-800
#6ed892
task-status-closed
#c0b9b1
#8f8f8d
task-status-closed-soft
#e0dcd7
#32322f
task-status-closed-strong
#3e3b38palette-stone-800
#9e9e9b
Token浅色深色
presence-online
#a9d877
#a9d877
presence-busy
#ffd440
#ffd440
presence-error
#f8a16f
#f8a16f
presence-offline
#c0b9b1
#c0b9b1
presence-external
#27ccf3
#27ccf3

贴纸

用法:贴纸。

Token浅色深色
sticker-ink
#141111
#141111
sticker-paper
#ffffff
#ffffff
sticker-edge
#141111 / 0%
#fafaf7 / 35%
sticker-mediumHover tier medium on a colour sticker (primary, warning and danger buttons, colour badges, the selected segment on press): hover and press, 10% ink in both modes.
#141111 / 10%
#141111 / 10%

阴影和焦点环

用法:层级、无障碍。

Token浅色深色
elevation-floatControls never cast a shadow; only overlays float.
0 1px 0 oklch(0.1813 0.005 17.62 / 0.039), 0 12px 32px -12px oklch(0.1813 0.005 17.62 / 0.322)
0 0 0 1px oklch(0.9843 0.004 106.47 / 0.078), 0 16px 40px -12px oklch(0 0 0 / 0.8)
elevation-sticker
4px 4px 0 oklch(0.1813 0.005 17.62)
4px 4px 0 oklch(0 0 0)
Token浅色深色
focus-ringThe focus indicator. It shows only after keyboard navigation: styles.css empties it while the root carries data-input-modality="pointer", which installInputModality sets after a click or a tap.
0 0 0 2px oklch(1 0 0), 0 0 0 4px oklch(0.1813 0.005 17.62)
0 0 0 2px oklch(0.2213 0.0038 106.71), 0 0 0 4px oklch(0.9211 0.004 106.48)

尺寸

输入框和卡片标题与旁边并排部件共用的文字尺寸。用法:字体排印。

Token浅色深色桌面端
field-font-size16px16px14px
field-font-weight400400
field-line-height24px24px22px
Token浅色深色桌面端
card-title-font-size16px16px14px
card-title-font-weight600600
card-title-line-height24px24px22px

控件尺寸:默认控件的高度、它的图标和点击区域,以及嵌在外框里的控件保持的值:control-embedded-inset,即它到外框外边缘和到相邻控件的距离;control-embedded-size,即输入框里标签或按钮的高度(间距、形状)。

Token浅色深色桌面端
control-heightA default control: a field, a Button at lg, an icon button at icon-lg. A finger's 44px on a phone, Feishu's 32px on a desktop.44px44px32px
control-height-ctaThe full-width call to action across a sign-in, sign-up or setup form (Button xl).52px52px40px
control-icon-sizeThe icon in a control-height icon button.22px22px18px
control-hit-insetHow far a control-height button's hit area reaches past its top and bottom, to keep 44px.0px0px6px
control-embedded-insetHow far a chip or a button embedded in a field, the composer or a corner sits inside its frame's outer edge, and from its sibling.6px6px
control-embedded-sizeA chip or a button embedded in a field: the control height less the embedded inset above and below, so the field keeps its height.32px32px20px

时间线节奏:对话里各行之间的消息档和分段档,以及每行在文字周围保留的行内距(间距)。

Token浅色深色
timeline-gap-messageThe message step of a timeline: from the last line of one turn to the first line of the next (a sender line, an event line). Every row keeps the row inset above and below its text; the first row of a turn adds the rest above it.16px16px
timeline-gap-sectionThe section step of a timeline, one step wider than the message step: either side of a date line, from the top of the list to the first line, and from the last line to the composer, so a boundary reads as one and the last message never sits on the composer.24px24px
timeline-row-insetHow far a timeline row's hover and selected highlight reaches above and below its text. Two rows of one turn sit two insets apart.4px4px

在 styles.css 中定义的 token

这些是 styles.css 里的 Tailwind 主题值,不是 tokens.json 中的条目,所以没有生成的表格。

Token归属
radius-sticker、radius-chip、radius-control、radius-container、radius-dialog形状
text-hero 到 text-label、text-mono-meta、text-mono-code、text-control,各有手机和桌面端两个值字体排印
ease-slide动效
spacing-timeline-message、spacing-timeline-section、spacing-timeline-row(时间线 token 对应的间距工具类),以及由它们推出的 spacing-timeline-turn、spacing-timeline-lead、spacing-timeline-start、spacing-timeline-end。产品只用 timeline-start 和 timeline-end间距
font-sans、font-heading、font-mono字体排印

弃用的名称

旧名称作为别名保留到 raft-ui 1.0,放在 tokens.json 的 deprecated 分组里。每个别名都指向应改用的 token,这样旧代码在迁移期间仍能正常工作。

Token浅色深色
fill-pressed#141111 / 8%fill-medium#fafaf7 / 7%fill-medium
fill-frame-pressed#141111 / 10%fill-frame-medium#fafaf7 / 6%fill-frame-medium
fill-row-hover#141111 / 4%fill-subtle#fafaf7 / 3%fill-subtle
fill-row-open#141111 / 4%fill-subtle#fafaf7 / 3%fill-subtle
fill-message-hover#141111 / 4%fill-subtle#fafaf7 / 3%fill-subtle
sticker-pressed#141111 / 10%sticker-medium#141111 / 10%sticker-medium
layer-panel#fffffflayer-canvas#1b1b19layer-canvas
layer-inset#f3f3f3fill-muted#32322ffill-muted
layer-canvas-muted#fffaeflayer-sidebar#0d0d0blayer-sidebar
layer-hud-foreground#f3f3f5foreground-on-hud#e5e5e2foreground-on-hud
foreground-strong#141111foreground#e5e5e2foreground
foreground-icon#141111 / 68%foreground-muted#c4c4c1foreground-muted
foreground-placeholder#141111 / 62%foreground-hint#9e9e9bforeground-hint
line#141111 / 45%line-seam#fafaf7 / 26%line-seam
line-muted#141111 / 15%line-hairline#fafaf7 / 10%line-hairline
primary-50#fff9edbrand-50#fff9edbrand-50
primary-100#fff6e3brand-100#fff6e3brand-100
primary-200#ffe9b9brand-200#ffe9b9brand-200
primary-300#ffdf91brand-300#ffdf91brand-300
primary-400#ffd440brand-400#ffd440brand-400
primary-500#d3ad03brand-500#d3ad03brand-500
primary-600#a78802brand-600#a78802brand-600
primary-700#7a6300brand-700#7a6300brand-700
primary-800#534300brand-800#534300brand-800
primary-900#2d2300brand-900#2d2300brand-900
primary-950#1c1500brand-950#1c1500brand-950
primary-strong#564919brand-strong#ffd440brand-strong
primary-soft#fff0bdbrand-soft#876f1f / 20%brand-soft
primary-edge#141111brand-edge#141111brand-edge
primary-glow#ffd440brand-glow#ffd440brand-glow
primary-hovercolor-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))brand-hovercolor-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))brand-hover
primary-activecolor-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))brand-activecolor-mix(in srgb, var(--brand-400) 90%, var(--sticker-ink))brand-active
accent-50#fff0f4attention-50#fff0f4attention-50
accent-100#ffe1e8attention-100#ffe1e8attention-100
accent-200#fec1d2attention-200#fec1d2attention-200
accent-300#fea0bcattention-300#fea0bcattention-300
accent-400#ff99c3attention-400#ff99c3attention-400
accent-500#fe7da8attention-500#fe7da8attention-500
accent-600#d4086fattention-600#d4086fattention-600
accent-700#9f0551attention-700#9f0551attention-700
accent-800#700238attention-800#700238attention-800
accent-900#42011eattention-900#42011eattention-900
accent-950#2f0014attention-950#2f0014attention-950
accent-strong#c00064attention-strong#ffacc7attention-strong
accent-soft#ffd6e2attention-soft#d0457c / 16%attention-soft
accent-hovercolor-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))attention-hovercolor-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))attention-hover
accent-activecolor-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))attention-activecolor-mix(in srgb, var(--attention-400) 90%, var(--sticker-ink))attention-active
theme-shadow-xs
0 0 #0000
0 0 #0000
theme-shadow-sm
0 0 #0000
0 0 #0000
theme-shadow-md
0 0 #0000
0 0 #0000
theme-shadow-lg0 1px 0 oklch(0.1813 0.005 17.62 / 0.039), 0 12px 32px -12px oklch(0.1813 0.005 17.62 / 0.322)elevation-float0 0 0 1px oklch(0.9843 0.004 106.47 / 0.078), 0 16px 40px -12px oklch(0 0 0 / 0.8)elevation-float
theme-shadow-xl0 1px 0 oklch(0.1813 0.005 17.62 / 0.039), 0 12px 32px -12px oklch(0.1813 0.005 17.62 / 0.322)elevation-float0 0 0 1px oklch(0.9843 0.004 106.47 / 0.078), 0 16px 40px -12px oklch(0 0 0 / 0.8)elevation-float
theme-shadow-float0 1px 0 oklch(0.1813 0.005 17.62 / 0.039), 0 12px 32px -12px oklch(0.1813 0.005 17.62 / 0.322)elevation-float0 0 0 1px oklch(0.9843 0.004 106.47 / 0.078), 0 16px 40px -12px oklch(0 0 0 / 0.8)elevation-float
theme-shadow-sticker4px 4px 0 oklch(0.1813 0.005 17.62)elevation-sticker4px 4px 0 oklch(0 0 0)elevation-sticker
theme-focus-ring0 0 0 2px oklch(1 0 0), 0 0 0 4px oklch(0.1813 0.005 17.62)focus-ring0 0 0 2px oklch(0.2213 0.0038 106.71), 0 0 0 4px oklch(0.9211 0.004 106.48)focus-ring
color-brutal-yellow-50#fff9edpalette-yellow-50#fff9edpalette-yellow-50
color-brutal-yellow-100#fff6e3palette-yellow-100#fff6e3palette-yellow-100
color-brutal-yellow-200#ffe9b9palette-yellow-200#ffe9b9palette-yellow-200
color-brutal-yellow-300#ffdf91palette-yellow-300#ffdf91palette-yellow-300
color-brutal-yellow-400#ffd441palette-yellow-400#ffd441palette-yellow-400
color-brutal-yellow-500#d3ad03palette-yellow-500#d3ad03palette-yellow-500
color-brutal-yellow-600#a78802palette-yellow-600#a78802palette-yellow-600
color-brutal-yellow-700#7a6300palette-yellow-700#7a6300palette-yellow-700
color-brutal-yellow-800#534300palette-yellow-800#534300palette-yellow-800
color-brutal-yellow-900#2d2300palette-yellow-900#2d2300palette-yellow-900
color-brutal-yellow-950#1c1500palette-yellow-950#1c1500palette-yellow-950
color-brutal-purple-50#f7f6fcpalette-lavender-50#f7f6fcpalette-lavender-50
color-brutal-purple-100#efecf9palette-lavender-100#efecf9palette-lavender-100
color-brutal-purple-200#dcd7f2palette-lavender-200#dcd7f2palette-lavender-200
color-brutal-purple-300#ccc4ecpalette-lavender-300#ccc4ecpalette-lavender-300
color-brutal-purple-400#bbafe6palette-lavender-400#bbafe6palette-lavender-400
color-brutal-purple-500#9d8adapalette-lavender-500#9d8adapalette-lavender-500
color-brutal-purple-600#7f62cbpalette-lavender-600#7f62cbpalette-lavender-600
color-brutal-purple-700#6341b0palette-lavender-700#6341b0palette-lavender-700
color-brutal-purple-800#442b7cpalette-lavender-800#442b7cpalette-lavender-800
color-brutal-purple-900#251548palette-lavender-900#251548palette-lavender-900
color-brutal-purple-950#170c31palette-lavender-950#170c31palette-lavender-950
color-brutal-pink-50#fff0f4palette-pink-50#fff0f4palette-pink-50
color-brutal-pink-100#ffe1e8palette-pink-100#ffe1e8palette-pink-100
color-brutal-pink-200#fec1d2palette-pink-200#fec1d2palette-pink-200
color-brutal-pink-300#fea0bcpalette-pink-300#fea0bcpalette-pink-300
color-brutal-pink-400#fe7da8palette-pink-400#fe7da8palette-pink-400
color-brutal-pink-500#fe2f8bpalette-pink-500#fe2f8bpalette-pink-500
color-brutal-pink-600#d4086fpalette-pink-600#d4086fpalette-pink-600
color-brutal-pink-700#9f0551palette-pink-700#9f0551palette-pink-700
color-brutal-pink-800#700238palette-pink-800#700238palette-pink-800
color-brutal-pink-900#42011epalette-pink-900#42011epalette-pink-900
color-brutal-pink-950#2f0014palette-pink-950#2f0014palette-pink-950
color-brutal-cyan-50#ecf9ffpalette-cyan-50#ecf9ffpalette-cyan-50
color-brutal-cyan-100#d7f2fepalette-cyan-100#d7f2fepalette-cyan-100
color-brutal-cyan-200#a9e6fepalette-cyan-200#a9e6fepalette-cyan-200
color-brutal-cyan-300#68dafdpalette-cyan-300#68dafdpalette-cyan-300
color-brutal-cyan-400#28ccf3palette-cyan-400#28ccf3palette-cyan-400
color-brutal-cyan-500#1ea6c6palette-cyan-500#1ea6c6palette-cyan-500
color-brutal-cyan-600#15849fpalette-cyan-600#15849fpalette-cyan-600
color-brutal-cyan-700#0e6276palette-cyan-700#0e6276palette-cyan-700
color-brutal-cyan-800#06414fpalette-cyan-800#06414fpalette-cyan-800
color-brutal-cyan-900#02252fpalette-cyan-900#02252fpalette-cyan-900
color-brutal-cyan-950#01161cpalette-cyan-950#01161cpalette-cyan-950
color-brutal-orange-50#fef5f1palette-orange-50#fef5f1palette-orange-50
color-brutal-orange-100#fde6depalette-orange-100#fde6depalette-orange-100
color-brutal-orange-200#fbd1c0palette-orange-200#fbd1c0palette-orange-200
color-brutal-orange-300#f9b899palette-orange-300#f9b899palette-orange-300
color-brutal-orange-400#f8a16fpalette-orange-400#f8a16fpalette-orange-400
color-brutal-orange-500#dd7e34palette-orange-500#dd7e34palette-orange-500
color-brutal-orange-600#ad6127palette-orange-600#ad6127palette-orange-600
color-brutal-orange-700#83481apalette-orange-700#83481apalette-orange-700
color-brutal-orange-800#5b310fpalette-orange-800#5b310fpalette-orange-800
color-brutal-orange-900#331905palette-orange-900#331905palette-orange-900
color-brutal-orange-950#220e02palette-orange-950#220e02palette-orange-950
color-brutal-lime-50#f1fde9palette-lime-50#f1fde9palette-lime-50
color-brutal-lime-100#dcfac1palette-lime-100#dcfac1palette-lime-100
color-brutal-lime-200#c0f588palette-lime-200#c0f588palette-lime-200
color-brutal-lime-300#b4e67fpalette-lime-300#b4e67fpalette-lime-300
color-brutal-lime-400#a9d877palette-lime-400#a9d877palette-lime-400
color-brutal-lime-500#8ab261palette-lime-500#8ab261palette-lime-500
color-brutal-lime-600#6b8b4apalette-lime-600#6b8b4apalette-lime-600
color-brutal-lime-700#506836palette-lime-700#506836palette-lime-700
color-brutal-lime-800#344522palette-lime-800#344522palette-lime-800
color-brutal-lime-900#1c2711palette-lime-900#1c2711palette-lime-900
color-brutal-lime-950#101808palette-lime-950#101808palette-lime-950
color-brutal-red-50#fef1f0palette-red-50#fef1f0palette-red-50
color-brutal-red-100#fddeddpalette-red-100#fddeddpalette-red-100
color-brutal-red-200#fbbdb9palette-red-200#fbbdb9palette-red-200
color-brutal-red-300#fa9991palette-red-300#fa9991palette-red-300
color-brutal-red-400#f97264palette-red-400#f97264palette-red-400
color-brutal-red-500#eb4423palette-red-500#eb4423palette-red-500
color-brutal-red-600#bb3419palette-red-600#bb3419palette-red-600
color-brutal-red-700#8e2510palette-red-700#8e2510palette-red-700
color-brutal-red-800#621708palette-red-800#621708palette-red-800
color-brutal-red-900#3e0b03palette-red-900#3e0b03palette-red-900
color-brutal-red-950#280501palette-red-950#280501palette-red-950
color-brutal-stone-50#f7f6f5palette-stone-50#f7f6f5palette-stone-50
color-brutal-stone-100#efedebpalette-stone-100#efedebpalette-stone-100
color-brutal-stone-200#e0dcd7palette-stone-200#e0dcd7palette-stone-200
color-brutal-stone-300#d2cbc2palette-stone-300#d2cbc2palette-stone-300
color-brutal-stone-400#c0b9b1palette-stone-400#c0b9b1palette-stone-400
color-brutal-stone-500#9d9891palette-stone-500#9d9891palette-stone-500
color-brutal-stone-600#7b7671palette-stone-600#7b7671palette-stone-600
color-brutal-stone-700#5d5955palette-stone-700#5d5955palette-stone-700
color-brutal-stone-800#3e3b38palette-stone-800#3e3b38palette-stone-800
color-brutal-stone-900#23211fpalette-stone-900#23211fpalette-stone-900
color-brutal-stone-950#141312palette-stone-950#141312palette-stone-950
color-brutal-cream-50#fffdf9palette-cream-50#fffdf9palette-cream-50
color-brutal-cream-100#fffcf5palette-cream-100#fffcf5palette-cream-100
color-brutal-cream-200#fffaefpalette-cream-200#fffaefpalette-cream-200
color-brutal-cream-300#f9eccfpalette-cream-300#f9eccfpalette-cream-300
color-brutal-cream-400#ebd38fpalette-cream-400#ebd38fpalette-cream-400
color-brutal-cream-500#d2b100palette-cream-500#d2b100palette-cream-500
color-brutal-cream-600#a78c00palette-cream-600#a78c00palette-cream-600
color-brutal-cream-700#7c6800palette-cream-700#7c6800palette-cream-700
color-brutal-cream-800#554500palette-cream-800#554500palette-cream-800
color-brutal-cream-900#312700palette-cream-900#312700palette-cream-900
color-brutal-cream-950#191300palette-cream-950#191300palette-cream-950
secondary-50#f7f6f5palette-stone-50#f7f6f5palette-stone-50
secondary-100#efedebpalette-stone-100#efedebpalette-stone-100
secondary-200#e0dcd7palette-stone-200#e0dcd7palette-stone-200
secondary-300#d2cbc2palette-stone-300#d2cbc2palette-stone-300
secondary-400#c0b9b1palette-stone-400#c0b9b1palette-stone-400
secondary-500#9d9891palette-stone-500#9d9891palette-stone-500
secondary-600#7b7671palette-stone-600#7b7671palette-stone-600
secondary-700#5d5955palette-stone-700#5d5955palette-stone-700
secondary-800#3e3b38palette-stone-800#3e3b38palette-stone-800
secondary-900#23211fpalette-stone-900#23211fpalette-stone-900
secondary-950#141312palette-stone-950#141312palette-stone-950

重命名遵循几种模式:

text
primary-*                  ->  brand-*
accent-*                   ->  attention-*
color-brutal-{color}-{n}   ->  palette-{color}-{n}   (purple -> lavender)
secondary-{n}              ->  palette-stone-{n}
layer-panel                ->  layer-canvas
layer-canvas-muted         ->  layer-sidebar
layer-inset                ->  fill-muted
layer-hud-foreground       ->  foreground-on-hud
foreground-strong          ->  foreground
foreground-icon            ->  foreground-muted
foreground-placeholder     ->  foreground-hint
line                       ->  line-seam
line-muted                 ->  line-hairline
theme-shadow-float/lg/xl   ->  elevation-float       (utility: shadow-float)
theme-shadow-sticker       ->  elevation-sticker     (utility: shadow-sticker)
theme-focus-ring           ->  focus-ring            (utility: shadow-focus)
theme-shadow-xs/sm/md      ->  none: controls cast no shadow, drop the class

Codemod

raft-ui 附带一个 codemod,可以改写 Tailwind 工具类(bg-…、hover:border-…/50)和 CSS 自定义属性(var(--…))中的旧名称:

sh
node node_modules/@botiverse/raft-ui/codemods/token-names.mjs <dir-or-file>... [--dry]
  • 它改写 .ts、.tsx、.js、.jsx、.mjs、.cjs、.css 和 .html 文件,跳过 node_modules、dist 和 build。--dry 列出它会修改的文件。
  • 它只重命名引用,不动声明,所以运行两次也是安全的。
  • 人工检查结果。原本禁止旧名称的防护(lint 规则、测试、允许列表)现在会禁止新名称;由片段拼出的模式,比如针对 class 名的正则表达式,仍保留旧名称。
  • 文档从不使用弃用的名称:页面中出现时,scripts/check-docs.mjs 会报错。

修改 token

  • 只在 tokens.json 里添加或修改值,并在同一次改动中重新生成 foundation.css。
  • 新 token 的名字要遵循上面的命名规则;如果组件需要,在 styles.css 里加上它的工具类。
  • 重命名 token 时,把旧名称保留在 deprecated 分组里并指向新名称,同时把这次重命名加进 codemod。只在大版本中删除别名。
  • 文字、表面或淡色的值变化时,在两种模式下重新核对颜色中的对比度保证。
  • 在同一个 pull request 里更新这个 token 所属的基础页面(编写指南)。
  • 颜色:每个颜色 token 的用途,以及它保证的对比度。
  • 字体排印:文字样式和尺寸 token。
  • 层级:阴影和焦点环。
  • 深色模式:两种模式之间有哪些变化。