资讯动态

设计系统浏览器:为AI编码生成标准化视觉令牌与DESIGN.md

发布时间:2026/9/29 19:27:16 来源:尧图企业网站定制
1. 项目概述一个为AI编码时代量身定做的设计系统浏览器最近在折腾一个挺有意思的夜间MVP项目叫“设计系统浏览器”。这玩意儿本质上是一个可搜索的网页画廊但它解决的痛点非常具体在AI编码助手比如Claude、GPTs、Cursor等越来越普及的今天如何让这些“智能体”快速理解并复现一个项目的视觉语言传统的设计系统文档比如Figma文件或者Storybook对于人类设计师和开发者来说很直观但对于AI来说信息是割裂的。AI需要一份结构清晰、语义明确的“说明书”才能准确地生成符合特定设计规范的代码。这个项目就是把像Linear、Vercel、Supabase这些流行产品的设计系统从它们各自的实现中“提取”出来转换成一份标准化的、可预览、可对比、并且能一键导出为DESIGN.md文件的工具。你可以把它想象成一个设计系统的“博物馆”兼“翻译器”专门服务于“AI代理家庭”的开发者工具链。它的核心价值在于“可操作性”。你不再需要去翻十几个不同的官网对比它们的按钮圆角、阴影和色板。在这里你可以直接浏览12个精心挑选的设计系统实时看到它们的组件按钮、卡片、输入框等是如何被各自的“设计令牌”渲染出来的。更重要的是你可以一键生成一份完整的DESIGN.md文档丢进你的项目根目录你的AI编码伙伴就能立刻读懂这个项目的视觉风格从生成代码的第一行开始就保持一致性。这对于快速启动新项目、构建夜间MVP或者像ClawDash这样的内部工具来说效率提升是巨大的。2. 核心设计思路与方案选型2.1 为什么是“浏览器”而不是“文档库”这个项目的定位非常清晰它不是另一个设计系统灵感收集网站而是一个面向开发流程的实用工具。“浏览器”这个词用得很准它强调的是一种主动的、探索式的交互。用户进来不是为了被动阅读而是为了主动查找、对比并最终“拿走”一份能直接用的资产。我选择这个方向是基于几个实际的观察。首先设计系统的复用正在从“抄袭视觉”转向“复用逻辑”。大家不再满足于“这个颜色好看我抄一下色值”而是想知道“Linear的警告按钮在交互状态下的颜色变化逻辑是什么”。其次AI编码的崛起催生了对机器可读设计文档的刚性需求。一份给AI看的DESIGN.md其结构严谨性和数据完整性要求远高于给人看的宣传页。最后工具链的整合价值凸显。一个能无缝嵌入npm run dev和AI Agent调用之间的工具其粘性会非常高。因此方案设计上必须突出三点数据的结构化、预览的实时性、输出的即用性。这直接决定了后续的技术栈选择和实现路径。2.2 技术栈决策极简与纯粹原项目提到技术栈是TypeScript Vite React Tailwind CSS v4并且特别强调“没有外部UI库”。这个选择堪称点睛之笔背后有很强的逻辑支撑。为什么是Vite ReactVite提供了极致的开发体验和构建速度这对于需要快速迭代的夜间MVP项目至关重要。React的组件化模型与设计系统的“令牌-组件”映射关系天然契合。每一个设计系统都可以被建模为一个React Context或一组Provider里面包含了所有的tokens颜色、字体、间距等而预览组件则是消费这些tokens的纯展示组件。这种模式清晰、易于维护。为什么必须用Tailwind CSS v4且不用任何UI库这是整个项目的灵魂所在。Tailwind CSS本身就是一个基于效用类的设计系统框架。v4版本对设计令牌CSS自定义属性的支持更加成熟。项目的关键技巧在于将每个第三方设计系统的视觉规范完全翻译成一套对应的CSS自定义属性集合。 例如Linear系统的--primary颜色变量对应其品牌蓝色。然后项目内的所有基础组件Button、Card等的样式完全通过className绑定这些CSS变量来实现。这样做的好处是纯粹性预览展示的是设计令牌最原始的应用效果没有受到任何第三方UI库如MUI, Ant Design自带样式或交互逻辑的“污染”。用户看到的就是令牌本身的力量。一致性比较模式得以实现。因为所有系统都使用同一套基础HTML结构和CSS变量应用逻辑差异仅在于变量值。这使得“侧边对比”功能变得非常公平和直观。可移植性最终导出的DESIGN.md文档其核心就是这些CSS自定义属性的定义。开发者或AI可以轻松地将这套变量体系植入自己的Tailwind配置或CSS-in-JS方案中。如果引入了Chakra UI或AntD我们看到的将是这些库的组件在特定主题下的样子而不是设计系统令牌本身的直接渲染结果这就完全偏离了项目的核心目标。2.3 数据来源与结构化策略项目收录了12个系统涵盖开发工具如Linear, Vercel、SaaS如Stripe和创意主题如Dracula。这些系统的原始数据从哪里来通常有几个渠道官方CSS/设计文件直接从其官网或开源仓库如GitHub中提取CSS变量定义。例如Vercel的Geist设计系统在其官网的样式表中就明确定义了所有变量。设计令牌规范文件有些系统会提供tokens.json或类似格式的设计令牌导出。手动提取与校准对于没有明确公开令牌的系统需要通过浏览器开发者工具手动提取并结合其官方设计稿进行校准确保预览的准确性。提取后的数据会被结构化为一个统一的TypeScript类型大概长这样interface DesignSystem { id: string; // 如 linear name: string; // 如 Linear mood: dark | light; category: string; tokens: { colors: Recordstring, string; // { --background-primary: #0a0a0a, ... } typography: Recordstring, { fontSize: string; fontWeight: number; lineHeight: string }; spacing: Recordstring, string; // { --spacing-1: 4px, ... } radii: Recordstring, string; shadows: Recordstring, string; }; }这份结构化的数据既是前端渲染的源数据也是生成DESIGN.md的模板数据源。3. 核心功能模块深度解析3.1 实时组件预览的实现机制这是用户体验的核心。页面上每个设计系统的卡片点击后应能立即展示一套使用该设计系统令牌渲染的UI组件。实现的关键在于动态CSS变量注入。当用户点击“Linear”系统时前端逻辑会做以下几件事获取令牌数据从存储中可能是本地一个systems.ts文件加载Linear系统的完整tokens对象。创建样式标签在文档的head中动态插入一个style标签或者更新一个特定的CSS类。注入CSS变量将tokens对象中的每一个键值对转换为CSS自定义属性规则。例如.theme-linear { --background-primary: #0a0a0a; --text-primary: #ffffff; --primary: #0052ff; /* ... 所有其他颜色、间距、圆角变量 */ }应用主题类将预览区域一个独立的div容器的类名设置为theme-linear。这个容器内的所有基础组件其样式都通过var(--primary)这样的方式引用变量。组件渲染预览区域内的Button、Card等组件是“纯净”的。它们没有固定的颜色其样式完全由CSS变量控制。例如一个按钮的样式可能是button classNamepx-4 py-2 rounded-lg bg-[var(--primary)] text-[var(--text-on-primary)] Click Me /button这样一来切换设计系统本质上就是切换预览容器上的CSS类名所有组件样式随之实时、无缝地更新。这种实现方式性能极佳且与框架无关。3.2 一键生成DESIGN.md的魔法这个功能是工具从“展示”走向“实用”的关键。生成的DESIGN.md文件需要具备两个特性人类可读和机器可解析。其生成逻辑是一个模板渲染过程。模板大致结构如下# Design System: [System Name] ## Colors | Token | Value | Usage | |-------|-------|-------| | --background-primary | #0a0a0a | Primary page background. | | --primary | #0052ff | Primary brand color for buttons, links. | ## Typography - **Font Family:** Inter, -apple-system, BlinkMacSystemFont, ... - **Scale:** - --text-xs: 0.75rem (12px) / line-height: 1rem ## Spacing - --spacing-1: 0.25rem (4px) - --spacing-2: 0.5rem (8px) ## Components ### Button - **Background:** var(--primary) - **Text Color:** var(--text-on-primary) - **Padding:** var(--spacing-3) var(--spacing-4) - **Border Radius:** var(--radius-md)实现时会遍历当前选中系统的tokens对象按照固定的Markdown模板格式将键值对填充进去。对于组件部分则需要预定义好一套对组件样式的描述性文本并与令牌变量关联。注意为了让AI更好地理解在DESIGN.md的开头或结尾可以加入一段简单的“提示词”例如“This file defines the visual design tokens for this project. Use these CSS custom properties when generating any UI component code.” 这能显式地引导AI编码代理关注这份文档。当用户点击“Export DESIGN.md”按钮时前端会调用BlobAPI和URL.createObjectURL方法将生成的Markdown字符串构建成一个文件对象并触发浏览器下载。代码示例如下const exportDesignMd (system) { const content generateMarkdownContent(system); // 调用模板渲染函数 const blob new Blob([content], { type: text/markdown }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download DESIGN-${system.id}.md; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); };3.3 对比模式架构与视觉差分“比较模式”是这个项目的另一个亮点。它不仅仅是并排摆放两个预览区更重要的是提供了令牌级别的差异对比。架构实现状态管理需要同时维护两个选中的设计系统System A和System B的状态。双重样式注入需要将两套CSS变量分别注入到两个独立的样式作用域中避免污染。通常会给两个预览容器分配不同的类名如.compare-theme-a和.compare-theme-b并分别注入对应的变量集。同步滚动与交互为了提升对比体验两个预览区域的滚动需要联动。这可以通过监听一个容器的scroll事件并同步设置另一个容器的scrollTop和scrollLeft属性来实现。令牌差异面板这是技术实现上更有挑战的部分。差异面板需要计算并高亮显示两个系统在关键令牌上的值的不同。数据提取与对齐首先需要确保比较的令牌“键名”是对应的。例如比较--primary颜色两个系统都必须有这个键。对于结构不同的系统可能需要一个“令牌映射表”来对齐语义如System A的--brand对应System B的--primary。差异计算与展示对于颜色可以计算其16进制或RGB值的差异对于尺寸如间距、圆角可以比较其数值。然后将差异可视化。// 简化的差异计算示例 const diffPanelData Object.keys(commonTokens).map(key ({ token: key, valueA: systemA.tokens.colors[key], valueB: systemB.tokens.colors[key], isDifferent: systemA.tokens.colors[key] ! systemB.tokens.colors[key] }));UI呈现将diffPanelData渲染为一个表格对isDifferent为真的行进行高亮如黄色背景。甚至可以加入一个颜色差异的视觉示例块直观展示两种蓝色的区别。4. 开发实操与关键实现步骤4.1 项目初始化与核心依赖我们从零开始搭建这个项目。首先使用Vite的ReactTypeScript模板快速初始化并安装Tailwind CSS v4。npm create vitelatest design-system-browser -- --template react-ts cd design-system-browser npm install -D tailwindcssnext postcss autoprefixer npx tailwindcss init -p接下来需要配置tailwind.config.js以支持CSS变量。在v4中我们可以直接在css中定义变量然后在配置中引用。但为了项目的动态性我们选择不将变量预设在Tailwind配置里而是完全通过内联和动态样式控制。核心的依赖就是这些。我们刻意不安装headlessui/react、framer-motion等库以保持极简。交互状态如按钮hover将完全用CSS实现。4.2 设计系统数据层的构建这是项目的基石。我们在src/data/systems.ts中定义所有系统的数据。以Linear系统为例export const linearSystem: DesignSystem { id: linear, name: Linear, mood: dark as const, category: Dev Tools, tokens: { colors: { --background-primary: #0a0a0a, --background-secondary: #111111, --primary: #0052ff, --primary-hover: #1a6bff, --text-primary: #ffffff, --text-secondary: #888888, --border: #333333, --success: #00c853, --warning: #ff9800, --error: #f44336, }, typography: { --font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, sans-serif, --text-xs: 0.75rem, --text-sm: 0.875rem, // ... 其他字号 }, spacing: { /* ... */ }, radii: { /* ... */ }, shadows: { /* ... */ }, }, };你需要为12个系统都手动创建这样的数据对象。这是一个繁琐但至关重要的“数据爬取”过程准确性直接决定了预览和生成文档的质量。4.3 动态主题注入与上下文管理我们需要一个全局状态来管理当前选中的主题。使用React Context是一个好选择。// src/context/ThemeContext.tsx import React, { createContext, useContext, useState } from react; import { DesignSystem } from ../data/systems; interface ThemeContextType { currentSystem: DesignSystem | null; setCurrentSystem: (sys: DesignSystem) void; compareSystems: [DesignSystem | null, DesignSystem | null]; setCompareSystems: (sys: [DesignSystem | null, DesignSystem | null]) void; } const ThemeContext createContextThemeContextType | undefined(undefined); export const ThemeProvider: React.FC{ children: React.ReactNode } ({ children }) { const [currentSystem, setCurrentSystem] useStateDesignSystem | null(null); const [compareSystems, setCompareSystems] useState[DesignSystem | null, DesignSystem | null]([null, null]); return ( ThemeContext.Provider value{{ currentSystem, setCurrentSystem, compareSystems, setCompareSystems }} {children} /ThemeContext.Provider ); };然后创建一个ThemeInjector /组件它监听currentSystem的变化并动态向head中注入样式。// src/components/ThemeInjector.tsx import { useEffect } from react; import { useTheme } from ../context/ThemeContext; export const ThemeInjector () { const { currentSystem } useTheme(); useEffect(() { const styleId dynamic-theme-styles; let styleEl document.getElementById(styleId) as HTMLStyleElement; if (!styleEl) { styleEl document.createElement(style); styleEl.id styleId; document.head.appendChild(styleEl); } if (currentSystem) { const cssVars Object.entries(currentSystem.tokens.colors) .map(([key, value]) ${key}: ${value};) .join(\n); styleEl.textContent .theme-preview { ${cssVars} font-family: ${currentSystem.tokens.typography[--font-family]}; } ; } else { styleEl.textContent ; // 清空样式 } return () { // 清理 }; }, [currentSystem]); return null; // 此组件不渲染任何UI };在预览组件中我们只需要给容器加上theme-preview类名即可。4.4 基础预览组件的实现为了保证公平对比所有预览组件Button, Card, Badge, Input等都必须使用相同的HTML结构和基础的CSS类只通过CSS变量来赋予样式。// src/components/preview/ButtonPreview.tsx export const ButtonPreview: React.FC () { return ( div classNamespace-y-4 h4 classNametext-sm font-semibold text-[var(--text-secondary)]Button/h4 div classNameflex flex-wrap gap-3 button classNamepx-4 py-2 rounded-lg bg-[var(--primary)] text-[var(--text-on-primary)] font-medium hover:opacity-90 transition-opacity Primary /button button classNamepx-4 py-2 rounded-lg border border-[var(--border)] text-[var(--text-primary)] bg-transparent hover:bg-[var(--background-secondary)] transition-colors Secondary /button button classNamepx-4 py-2 rounded-lg bg-[var(--error)] text-white font-medium hover:opacity-90 Destructive /button button classNamepx-4 py-2 rounded-lg opacity-50 cursor-not-allowed disabled Disabled /button /div /div ); };注意这里使用了bg-[var(--primary)]这样的Tailwind CSS任意值语法来直接引用CSS变量。颜色、边框、背景色等所有视觉属性都通过变量控制。4.5 对比模式视图的实现对比视图需要渲染两个独立的预览区域并共享同一套预览组件。// src/components/CompareView.tsx import { useTheme } from ../context/ThemeContext; import { ButtonPreview, CardPreview /* ... */ } from ./preview; export const CompareView: React.FC () { const { compareSystems } useTheme(); const [sysA, sysB] compareSystems; if (!sysA || !sysB) return div请选择两个系统进行比较/div; return ( div classNameflex flex-col lg:flex-row gap-8 {/* 系统A预览区 */} div classNameflex-1 div classNamesticky top-0 bg-white dark:bg-gray-900 p-4 border-b h3 classNamefont-bold{sysA.name}/h3 /div div classNamep-6 theme-preview-a {/* 注意这里类名不同 */} ButtonPreview / CardPreview / {/* ... 其他组件 */} /div /div {/* 分隔线与差异面板 */} div classNamehidden lg:block border-l relative DiffPanel systemA{sysA} systemB{sysB} / /div {/* 系统B预览区 */} div classNameflex-1 div classNamesticky top-0 bg-white dark:bg-gray-900 p-4 border-b h3 classNamefont-bold{sysB.name}/h3 /div div classNamep-6 theme-preview-b ButtonPreview / {/* 同样的组件但因容器类名不同而呈现不同样式 */} CardPreview / {/* ... */} /div /div /div ); };这里的关键是我们需要两个主题注入器或者一个能同时注入两套变量的注入器分别对应.theme-preview-a和.theme-preview-b类。5. 部署、优化与未来扩展思考5.1 构建与部署实践项目使用Vite构建命令很简单。但原项目说明中有一个细节npm run build之后执行了mv dist out。这通常是为了适配某些静态站点托管平台如Vercel的默认输出目录要求或者仅仅是个人习惯。你可以根据你的部署平台调整。npm run build # 构建后dist目录下的文件就是最终的静态资源部署就是将这些静态文件index.html,assets/文件夹上传到任何静态托管服务如GitHub Pages, Netlify, Vercel, Cloudflare Pages等。由于是纯前端应用无需服务器端渲染部署过程非常轻松。5.2 性能优化与用户体验细节CSS变量注入优化频繁地创建和移除style标签可能引发重排。更好的做法是预先为每个系统创建好对应的CSS类样式并全部注入到文档中通过切换body或容器上的类名如>.theme-linear { --primary: var(--linear-primary); --background: var(--linear-background); }颜色对比度与可访问性在对比模式下展示颜色差异的同时可以计算并展示两个颜色在WCAG标准下的对比度比率为开发者提供可访问性参考。令牌语义化映射有些系统使用--color-gray-800有些用--background-secondary。在生成DESIGN.md和对比时需要建立一套“语义化映射表”primary, secondary, background, border等将不同系统的物理令牌映射到统一的语义上这样对比和导出才更有意义。构建产物优化将所有系统的令牌数据打包进JS bundle会导致文件体积变大。可以考虑在构建时将这些数据分离按需加载或者直接作为静态JSON文件在运行时获取。这个项目最让我兴奋的一点是它精准地捕捉到了开发工作流中一个正在形成的断层并用一个极简的解决方案将其弥合。它不只是一个展示柜而是一个翻译器和转换器把设计师和品牌团队创造出的视觉语言翻译成开发者和AI都能精准执行的“机器指令”。在AI辅助开发日益成熟的今天这类工具的价值会越来越凸显。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑