关于 design-system
面向 snapcn 注册表组件开发者的设计系统指南,说明如何复用 shadcn 令牌与基础组件样式,并确保主题、渲染和参考设计之间保持一致。
主要功能
- 01同步主题令牌与站点样式
- 02复用基础组件的样式上下文
- 03按比例调整边框
- 04规范视频文字与字幕样式
- 05适配 Remotion 渲染器
适用场景
- 01为注册表组件支持主题覆盖
- 02统一组件与站点的视觉样式
- 03适配动画颜色与字体渲染
- 04根据参考设计还原组件比例
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ shadcn-labs/ snapcn --skill design-system安装后在 Claude Code 中输入/design-system确认已加载。
详细介绍
设计系统
snapcn 是一个 shadcn 注册表。这不是品牌定位,而是一项约束。我们发布的每个组件都会进入某人的 shadcn 项目,与他们的 Input 和 Button 并列,因此它看起来必须像是属于那里。自行绘制灰色调的场景组件,必然会与安装它的应用产生冲突。
因此:组件不得自行创造颜色、边框、阴影或圆角。 它们必须来自设计系统,或来自已经解决这些问题的原语。
规则 1 — 令牌是唯一可信来源
registry/snap-cn-ui/core/theme.ts 是一组 shadcn 令牌(SnapCnTheme),也是 app/globals.css 的镜像——与网站 components/ui/* 使用的值相同。这不是靠手动维护的巧合:两者只要不一致,pnpm run check:tokens 就会失败。设置这项检查,是因为两者确实曾经不一致,持续了很长一段时间,以至于网站的色调是暖色,而它展示的视频却是冷色调。
| token | 浅色 | 含义 |
|---|---|---|
background | #faf9f6 | 页面(暖调米白色——不是 #fff) |
card | #ffffff | 页面上的表面 |
foreground | #141414 | 文本(不是 #000) |
mutedForeground | #6e6a63 | 次要文本、前置图标 |
border / input | #d9d9d9 | 细线 |
ring / primary | #3577e0 | 焦点、强调色 |
radius | 10 | 控件 |
深色主题采用同一文件中的 .dark 区块(页面为 #0a0a0b,卡片为 #141417,细线为 #26272b,并沿用 #3577e0)。
切勿单独修改此表或 theme.ts。 令牌应先在 globals.css 中修改,因为 shadcn 组件遵循其中的设置;然后再更新 theme.ts,并通过 check:tokens 验证两者一致。唯一的例外是反向调整:设置 --radius: 0.28rem,让它的 --radius-3xl 级别对应这里的 10。
使用 hook 获取它们,切勿导入对象:
import { type SnapCnTheme, useSnapCnTheme } from "@/lib/snap-cn-ui";
const t = useSnapCnTheme(theme, mode); // prop > provider > light/dark default
并像其他组件一样,将 theme?: Partial<SnapCnTheme> 和 mode?: "light" | "dark" 作为 props。这样用户就能将组件放入自己的调色板,而无需分叉代码。
规则 2 — 复用原语的表面样式,不要重新推导
UI 层已经导出了各个控件用于绘制自身的样式上下文。如果某个字段想要呈现 shadcn 输入框的外观,就应从 shadcn 输入框获取表面样式:
import { inputStyleContext } from "@/components/snap-cn/input";
const ui = inputStyleContext(t);
// → idleBorder, hoverBorder, activeBorder, ring, background, foreground, mutedForeground
这样一来,两者就不会产生偏差。请在 registryDependencies 中声明该原语(@snapcn/input、@snapcn/caret、@snapcn/snap-cn-ui),以便安装你组件的用户也能获得它。
组合使用渲染后的原语则是另一回事,而且往往不是正确的做法——Input 是一个宽度为 320px 的控件,带有状态机和基于切片的文本显示效果。具有特定比例、裁剪显示效果和摄像机的主视觉字段,无法用它构建。取用它的令牌,不要取用它的盒子。
规则 3 — 阴影
问题就出在这里,而这个 bug 值得保留:
search-typing上线时用了rgba(9,9,12,0.45)的投影,模糊半径为控件高度的0.12 ×,因为参考视频就是这么做的。参考视频的背景是深紫色,厚重的阴影看起来很有立体感。把同一个组件放在白色页面上,阴影就成了控件下方的一片灰色污渍。它显得很廉价,也是所有人第一眼就注意到的问题。
把阴影弱化到「得仔细看才能发现」还是不对——下一轮反馈还是同一个词:最差。在这么大的控件下方,能看得见的投影放在浅色页面上就是一片灰色污渍,仅此而已。
默认完全不加投影。 shadcn 定义的控件带有一圈极细的边框,边框就足以完成全部工作:
background: t.card, // #ffffff
border: `1px solid ${ui.idleBorder}`, // #d9d9d9
boxShadow: "none",
渐变填充不过是同一个错误换了副面孔:参考视频里从灰色顶部过渡到白色的填充,放在浅色页面上看起来像内阴影。用纯色填充、极细边框,不加其他效果。
从参考素材中提取的阴影,只适用于该参考素材的背景。它不是组件本身的属性。如果想要那种受光表面效果,就把它作为可选项提供(surface="glass"),并说明它适用于哪种背景。
规则 3b — 令牌要按控件的比例定义,而不是按你的比例
去掉阴影后,接下来轮到边框:「边框非常浅。」确实如此——但令牌没有问题,问题在于比例。
#d9d9d9 用在 1px 边框上,放在 40px 高的控件上只是一圈极细的边线:占控件高度的 2.5%。把同样的边框放到 190px 高的主视觉输入框上,它就只占控件高度的 0.5%,几乎看不见。令牌的视觉分量取决于它所描边对象的大小。
因此要调整比例,但要通过系统来调整,而不是凭空造一种灰色:
const border = Math.max(1, 0.009 * H); // width: a ratio of the field
const borderColor = mixOklch(ui.idleBorder, t.foreground, 0.28); // contrast: the system's own mix
mixOklch 会在系统的色彩空间中将极细的边线向 foreground 方向调整,因此结果仍属于调色板,也会继续遵循用户的主题覆盖设置。不要因为令牌「看起来太浅」就改用十六进制颜色。
规则 3c — 烧录在视频中的文字不是应用界面元素
字幕、下三分之一字幕条和标题,是唯一不适用应用调色板的地方。它们烧录在视频素材中,旁边没有任何人的 Input,而且必须在人物面孔、天空或白色桌面上都清晰可读。决定它们效果的是制作工艺,而不是令牌:
- 在字形外侧绘制粗描边。
-webkit-text-stroke会让描边居中,并从字形内部侵占空间——实测时,14px 的描边会把 38px 粗的竖画压缩到 22px。paint-order: stroke fill会先绘制描边,再在上面绘制填充,竖画就能恢复到 38px。少了这一行,字幕看起来就很廉价,却没人说得出原因。 - 使用展示字体的字重和字号。 Montserrat 800–900,字号为画面短边的 11–13%。
word-captions用的是 Inter 700,字号仅为画面高度的 2.8%——名为字幕,实际却像副标题。 - 强调色就是设计本身。 字幕的黄色不是品牌令牌,而是其视觉风格。将它作为属性开放;不要使用
theme.primary。
组件带有应用界面元素时(例如胶囊形标签、卡片),中性色仍应取自系统。例外只有烧录在视频中的文字本身。
规则 4 — 令牌必须能通过渲染器处理
动画颜色必须是具体的 oklch/hex/rgb 颜色,并通过 mixOklch 插值。对于 JS 插值,Remotion 的无头渲染器无法解析 var(--token)——Remotion 打包内容中没有应用的 CSS。静态且永不参与动画的颜色,可以在内联 style 中使用 var();任何要做补间动画的颜色都不能这样用。
字体也有同样的陷阱:使用 var(--font-outfit),网站上会显示正确字体,渲染结果中却会回退到备用字体。用 @remotion/google-fonts 加载字体,确保 Player、mp4 和用户自己的项目显示效果一致。
规则 5 — 测量参考对象的比例,而非配色
基于参考对象建模组件时,几何尺寸值得精确测量到像素级——圆角半径/高度、帽高、描边、间距。这才是让它看起来像参考对象的关键。
它的颜色属于参考对象的品牌,而非你的品牌。Slack 的紫色、Slack 从玻璃质感灰色到白色的背景、Slack 的阴影:这些都不该出现在面向陌生用户发布的注册表组件中。借鉴形状,舍弃配色。
如果经过测量的表面样式确实值得保留,就将其作为选项与设计系统默认样式一同发布——surface="glass" 与默认的 surface="shadcn"——并在文档中说明它适用于哪种背景。