Sink 设计系统解析:DESIGN.md 中的 oklch 语义令牌与 shadcn-vue 组件约定
【免费下载链接】Sink⚡ A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.项目地址: https://gitcode.com/GitHub_Trending/si/Sink
本文基于 Sink(一个 100% 运行在 Cloudflare 上的短链接统计工具)仓库根目录的DESIGN.md展开。该文件是 Sink 的"语义设计令牌 + 组件指引"文档,专为设计感知型 Agent 与人协作而生。读完本文,你将完整掌握 Sink 界面的颜色角色体系(含明暗两套主题)、字体与圆角令牌、刻意保持"非全局化"的布局/阴影策略,以及按钮、卡片、对话框、Tabs、侧边栏等组件的变体约定,并能对照app/assets/css/tailwind.css与app/components/ui/**源码验证每个令牌的真实落点。
DESIGN.md 的定位:派生摘要,CSS 与组件实现是权威
DESIGN.md开头明确了自己的角色:
This file is a derived summary for design-aware agents. If it conflicts with
app/assets/css/tailwind.cssor a component underapp/components/ui/**, the CSS and UI component implementation are authoritative.
也就是说,它是从真实样式实现中派生出来的摘要文档,供 Agent 在生成 UI 时快速对齐设计规范;一旦它与 app/assets/css/tailwind.css 或 app/components/ui/ 下的组件实现冲突,以后者为准。文件由两部分组成:
- YAML frontmatter:机器可读的令牌声明(
version: alpha、name: Sink、colors、typography、rounded、components),其中组件样式通过{colors.primary}这类引用串接颜色令牌; - Markdown 正文:面向人的使用守则(Colors / Typography / Layout / Elevation / Shapes / Components / Interaction / Do's and Don'ts)。
这种"frontmatter 给 Agent 读、正文给人读"的双轨结构,是设计令牌文件被 LLM 直接消费的一种务实形态。
颜色令牌:以角色而非色值组织的全套体系
DESIGN.md 的核心主张是:永远按语义角色使用颜色,并且每个背景必须与其前景成对出现。前缀无修饰的令牌复现:root(浅色主题)中的语义角色,dark-*令牌则把.dark(深色主题)的对应值"拍平"成同名前缀令牌。
浅色主题(:root)令牌全集
以下值与 tailwind.css 的:root定义逐一对应:
| 令牌 | 值(oklch) | 语义用途 |
|---|---|---|
background/foreground | oklch(1 0 0)/oklch(0.141 0.005 285.823) | 页面底色与默认正文 |
card/card-foreground | 同 background/foreground | 卡片容器 |
popover/popover-foreground | 同 background/foreground | 下拉内容、浮层 |
primary/primary-foreground | oklch(0.21 0.006 285.885)/oklch(0.985 0 0) | 近黑主色 + 近白文字 |
secondary/secondary-foreground | oklch(0.967 0.001 286.375)/oklch(0.21 0.006 285.885) | 次级按钮等 |
muted/muted-foreground | oklch(0.967 0.001 286.375)/oklch(0.552 0.016 285.938) | 弱化背景与弱文字 |
accent/accent-foreground | 与 secondary 相同 | 悬停/激活高亮 |
success/success-foreground | oklch(0.55 0.16 145)/oklch(0.985 0 0) | 正向状态与完成指示 |
destructive | oklch(0.577 0.245 27.325) | 破坏性操作或无效状态 |
border/input | oklch(0.92 0.004 286.32) | 边框与输入框描边 |
ring | oklch(0.705 0.015 286.067) | 焦点强调 |
chart-1…chart-5 | 从oklch(0.871 0.15 154.449)到oklch(0.448 0.119 151.328)的绿色递进序列 | 仅供图表使用 |
sidebar系列 8 项 | sidebar为oklch(0.985 0 0),其余见下节 | 侧边栏专属角色 |
几条关键使用规则(DESIGN.md Colors 一节原文要旨):
- 绿色 chart-1 到 chart-5 序列只给图表用,不要挪作状态色;
success专用于正向状态/完成指示,ring专用于焦点强调,destructive专用于破坏性或无效状态——三者在深色模式都有对应值;- 深浅两套主题中都要保持背景/前景成对出现,禁止混搭。
深色主题(.dark):拍平的 dark-* 令牌
DESIGN.md 用dark-前缀把.dark选择器下的值拍平成独立令牌,例如dark-background: oklch(0.141 0.005 285.823)、dark-primary: oklch(0.92 0.004 286.32)。对照 tailwind.css 可以看到几个值得注意的深色主题设计决策:
- 透明度描边:
dark-border是oklch(1 0 0 / 10%)、dark-input是oklch(1 0 0 / 15%)——深色下用白色半透明替代实色描边,边框会随背景深浅自然变化; - 图表色跨主题不变:
dark-chart-1到dark-chart-5与浅色主题完全相同,保证同一数据集在明暗主题下颜色一致; - 深色侧边栏主色带蓝调:
dark-sidebar-primary: oklch(0.488 0.243 264.376)是整张令牌表中唯一的饱和蓝紫值(浅色主题的sidebar-primary则是与 primary 相同的近黑色),这是深色模式下侧边栏强调色被单独提亮的地方。
主题切换机制本身由 Nuxt 的useColorMode驱动:app/components/SwitchTheme.vue 提供 light / dark / system 三个选项,写入colorMode.preference,而@custom-variant dark (&:is(.dark *))(tailwind.css)让所有dark:工具类响应根节点上的.darkclass。
排版与圆角:两条极简令牌
Typography 一节明确:
- 界面文字统一使用IBM Plex Sans(
fontFamily: "IBM Plex Sans, sans-serif");heading只是同一字族的别名,不是独立的展示字体; - 字号、字重、行高由各组件用本地工具类自行决定——这个文件不定义全局字阶。
这一点在 tailwind.css 中得到印证:
--font-sans: 'IBM Plex Sans', sans-serif; --font-heading: var(--font-sans);--font-heading直接指向--font-sans,"heading 是别名"这句话就是这一行 CSS 的文字版。同时 components.json 里"font": "ibm-plex-sans"记录了 shadcn-vue CLI 初始化时选定的字体。
Shapes 一节给出唯一的形状令牌:基准圆角0.625rem(--radius: 0.625rem),派生圆角为:
sm = calc(var(--radius) - 4px) md = calc(var(--radius) - 2px) lg = var(--radius) xl = calc(var(--radius) + 4px)这段公式与 tailwind.css 的@theme inline块逐字对应。DESIGN.md 还特别强调:rounded-2xl、rounded-4xl这类工具类是组件的本地选择,不是全局设计令牌;"大圆角与紧凑密度共存,更大的圆角不意味着更大的控件"。
布局与层次:刻意不定义全局令牌
DESIGN.md 有两大节在"宣告克制":
- Layout:仓库不定义全局网格、容器、断点或间距比例尺。组合界面时"跟随相邻实现";侧边栏宽度与响应式行为属于 sidebar 组件自身,不是全局布局令牌。
- Elevation & Depth:基础表面靠语义背景、边框、ring 建立层次;对话框和浮层是否用遮罩/ring/阴影,取决于各自的组件实现——阴影是组件实现细节,不是全局层级令牌。
这种"令牌最小化"策略的源码级证据同样来自 tailwind.css:@theme inline块只映射了颜色、radius、字体,没有任何--spacing网格或断点覆盖;@layer base里只做了全局border-border outline-ring/50、bg-background text-foreground、细滚动条与prefers-reduced-motion降级(动画/过渡时长压到0.01ms),没有任何全局阴影变量。
组件约定:与 app/components/ui 实现一一对应
DESIGN.md 的 Components 一节逐组件列出了约定,下面结合源码逐一核对。
按钮:变体、尺寸与 data 属性
约定内容:default、outline、secondary、ghost、destructive、link 六类变体,多种文字/图标尺寸,focus/invalid ring,按下位移,禁用处理。
app/components/ui/button/Button.vue 的实现印证了"变体由 data 属性驱动"的模式:
<Primitive ><div >variant: { default: 'bg-muted', // 填充式:muted 背景 line: 'gap-1 bg-transparent' // 线性:透明背景 + 间距 }配合 app/components/ui/tabs/TabsTrigger.vue 中通过after:伪元素绘制激活指示条、按group-data-horizontal/group-data-vertical切换横纵方向的写法,DESIGN.md 里"横向/纵向状态"的描述有了具体出处。
侧边栏:专属令牌组与"已定义未消费"的诚实声明
约定:sidebar 使用专属的 background、foreground、accent、border、ring 角色,贯穿 responsive、collapsible、floating、inset 各形态;主导航空闲时用默认形状,hover 与 active 状态都用更强的 pill 处理;次级工具是紧凑的纯图标控件,依赖原生折叠行为。
DESIGN.md 还主动声明了一条容易被忽略的事实:sidebar-primary/sidebar-primary-foreground已定义但当前这些组件并未消费。这一点可以用仓库搜索验证:sidebar-primary只出现在 tailwind.css(浅色与深色各一处定义),而 app/components/ui/sidebar/ 下的组件实际消费的是text-sidebar-foreground、ring-sidebar-ring、hover:bg-sidebar-accent、data-active:bg-sidebar-accent等 accent/foreground/border/ring 角色(见 SidebarMenuButtonChild.vue 与 sidebar/index.ts 的工具类组合)。设计文档与源码互相印证:令牌表为"未来的侧边栏主色"预留了槽位,但当前实现走的是 accent 高亮路线。
组件使用纪律
DESIGN.md 的最后一句组件纪律值得单独引用:复用现有 UI 组件及其变体,不要手改app/components/ui/**下的生成组件。这些组件由 components.json 描述的 shadcn-vue 配置管理——style: "reka-maia"、cssVariables: true、baseColor: "zinc"、图标库lucide,ui别名指向@/components/ui。手改生成代码意味着下一次 CLI 同步/升级时会丢失本地修改,因此"新样式应落在业务组件(如 app/components/dashboard/)而不动 ui 层"是隐含的工作流。
交互约定
DESIGN.md Interaction 一节给出三条跨组件规则:
- 移动端默认保持 registry 控件尺寸,只有当上下文确实需要更大触控目标时才用更大的官方变体;
- 紧凑的上下文动作列表用 menu,更丰富的锚定内容用 popover——这与前文 select"触发器 + popover 内容"的分工是同一套判断;
- 浮层套浮层时,焦点直接进入新表面;流程结束后回到发起控件——这是标准的焦点管理约定,与 reka-ui 底层的 focus-trap 行为配合。
另外 tailwind.css 的 base 层为这条交互约定补了两个全局细节:所有可交互的role元素(button、menuitem、tab、option、switch 等)统一cursor-pointer,链接/按钮/输入统一touch-action: manipulation消除移动端双击缩放延迟。
Do's and Don'ts(原文守则)
DESIGN.md 以清单收尾,是整份规范的浓缩版:
Do
- 使用语义颜色;
- 在明暗两个主题中都保持背景/前景成对;
- 复用现有组件;
- 让 hover、focus、active、invalid、expanded、disabled 状态与相邻实现保持一致。
Don't
- 不要发明新令牌;
- 不要把生成出来的工具类选择(如某个
rounded-2xl)升格为全局令牌; - 不要手改
app/components/ui/**。
小结与延伸阅读
Sink 的DESIGN.md展示了小型项目中一套非常克制的设计令牌实践:颜色按角色组织、oklch 统一色彩空间、明暗主题成对定义、排版与圆角各留一条令牌、布局与阴影坚决不下沉为全局。frontmatter 服务 Agent 自动化,正文服务人类协作,且全文明确自己相对于 CSS 与组件实现是"派生摘要"的从属地位。
如需深入,可按以下路径继续:
- 令牌唯一事实来源:app/assets/css/tailwind.css
- shadcn-vue 生成配置:components.json
- 组件示例:Button.vue、Card.vue、tabs/index.ts、sidebar 组件目录
- 主题切换入口:SwitchTheme.vue
- 业务侧组件(令牌的实际消费方):app/components/dashboard/
【免费下载链接】Sink⚡ A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.项目地址: https://gitcode.com/GitHub_Trending/si/Sink
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考