Nango 设计系统(@nangohq/design-system)深度指南:设计 Token、Storybook 与 React 组件实战
【免费下载链接】nangoBuild product integrations with AI.项目地址: https://gitcode.com/GitHub_Trending/na/nango
导读
本文以 Nango 开源仓库中的packages/design-system包为核心,系统讲解这套共享设计系统的三大支柱:设计 Token 流水线(Figma → Tokens Studio → Style Dictionary → CSS 变量 → Tailwind v4 工具类)、Storybook 组件工作台(含 MCP 服务与无障碍审计)以及React 组件库(Radix UI + CVA + tailwind-merge 模式)。读完本文,你将掌握如何在本地启动 Storybook、如何同步并重建设计 Token、如何按约定新增一个组件变体或新组件,以及这套流水线在 Nango Web 应用中的消费方式。
本文所有路径均以仓库根目录为基准,涉及的核心文件见 packages/design-system/README.md。
一、包结构与定位
@nangohq/design-system是 Nango 应用(packages/webapp)共享的前端设计系统,涵盖三部分内容:
- 设计 Token(design tokens):由 Figma 中的 Tokens Studio 插件创作,编译为 CSS 自定义属性(custom properties);
- React 组件:按需添加、开箱即用的 UI 组件;
- Storybook:承载上述 token 与组件的“活样式指南”(living style guide)。
包的导出配置定义在 packages/design-system/package.json,主入口与 token CSS 通过exports字段暴露:
"exports": { ".": { "import": "./src/index.ts", "types": "./src/index.ts" }, "./tokens/tokens.generated.css": "./tokens/tokens.generated.css" }包名对外暴露两样东西:组件(从src/index.ts这个 barrel 文件导出)和编译好的 token CSS。所有公开导出集中在 packages/design-system/src/index.ts,包括Button、IconButton、buttonVariants、Alert、Badge、Dialog、Field、Input、InputGroup、Textarea、Tooltip、Card等组件及其variants对象,以及供消费方复用的dsTwMergeConfig。
从源码结构看,组件全部平铺在src/components/ui/目录下(shadcn 惯例),配合同名的*.stories.tsx故事文件;src/lib/cn.ts提供类名合并工具;tokens/目录存放 token 的“双源文件”。
二、Storybook:本地运行、主题切换与无障碍审计
Storybook 是这套设计系统的活样式指南,可在本地启动后浏览 token 与组件在亮色/暗色两种主题下的表现。
2.1 启动方式
# 从仓库根目录 npm run storybook # 或者直接在包目录内 cd packages/design-system npm run storybook启动后访问http://localhost:6006。点击右上角的Themes工具栏按钮,即可在亮色与暗色主题之间切换。
注意:
storybook脚本使用NODE_PATH=./node_modules来规避 Vite 7 的 CJS 模块解析问题(脚本定义见 packages/design-system/package.json)。该设置安全无害,应予以保留,不要移除。
仓库还提供了静态构建命令npm run build-storybook(对应build-storybook脚本)。与storybook dev不同的是,build-storybook会急切(eagerly)转换每一个故事,因此任何损坏的 import 都会导致构建失败——这在 dev server 下可能不会被加载到。AGENTS.md建议:当某个故事的 import 发生变化时,推送前先在本地运行npm run -w @nangohq/design-system build-storybook验证。
2.2 Storybook MCP:让 AI 助手直接读懂组件库
Storybook 自带 MCP 服务器(@storybook/addon-mcp,见 package.json 的 devDependencies),把故事的文档暴露给 AI 助手。这样可以让 Claude 在充分了解现有 stories、props 与用法示例的前提下构建或修改组件,无需手动复制粘贴文档。
MCP 服务默认关闭,以避免未运行 Storybook 的工程师遇到连接错误。需要启用时,在仓库根目录执行一次:
claude mcp add --transport http storybook http://localhost:6006/mcp该命令把配置写入.claude/settings.local.json(已被 gitignore)。然后启动 Storybook(npm run storybook)并重载 Claude Code,下面的工具就会自动可用:
| 工具 | 作用 |
|---|---|
list-all-documentation | 列出所有 story ID 与组件名 |
get-documentation | 返回某个组件的 props、变体与用法 |
preview-stories | 渲染一个 story 并返回预览 URL |
2.3 无障碍(Accessibility)审计
@storybook/addon-a11y会对每一个 story运行基于 axe-core 的自动化无障碍审计(依赖版本axe-core 4.12.0记录在 packages/design-system/package.json)。打开 Storybook UI 底部的Accessibility面板,即可看到当前渲染 story 的违规项(violations)、未完成检查(incomplete)与通过规则(passing)。组件发布前应修复所有违规项。
三、设计 Token:从 Figma 到 CSS 变量再到 Tailwind 工具类
3.1 创作与同步链路
设计 Token 在 Figma 中通过 Tokens Studio 插件创作,随后编译为 CSS 自定义属性。Tokens Studio 配置了GitHub sync,指向本仓库的design/tokens分支——这意味着设计师可以直接从 Figma 插件推送 token 变更,任何连接到同一 sync 配置的 Figma 文件都共享同一套 token。
3.2 目录结构
packages/design-system/ tokens/ tokens.json Tokens Studio 导出(事实源,提交到 git) tokens.generated.css 生成的 CSS 自定义属性(提交到 git) scripts/ tokens-fetch.mjs token 流水线脚本tokens.json是 Tokens Studio 的原始导出;tokens.generated.css是编译产物。两者都提交到 git。除 README 中列出的结构外,实际目录中还包含:
tokens/types.ts与tokens/stories/(ColorPalette.stories.tsx、Typography.stories.tsx),用于在 Storybook 中可视化展示色板与字体;scripts/tokens-check-removed.mjs(对应npm run tokens:check),用于检查 token 是否被移除。
3.3 同步工作流(Designer → Code)
设计师 → 代码:
- 在 Figma 中通过 Tokens Studio 插件编辑 token;
- 推送到
design/tokens分支(插件 Settings → Sync provider → GitHub); - 通知开发者运行
npm run tokens:fetch。
开发者拉取最新 token:
cd packages/design-system npm run tokens:fetch git add tokens/ git commit -m "chore(design-system): update tokens"仅重建 CSS、不拉取(例如解决了tokens.json的合并冲突之后):
npm run tokens:build这两个命令对应 packages/design-system/package.json 中的脚本:
"tokens:fetch": "node scripts/tokens-fetch.mjs", "tokens:build": "node scripts/tokens-fetch.mjs --build-only", "tokens:check": "node scripts/tokens-check-removed.mjs"3.4 流水线底层实现(源码解读)
packages/design-system/scripts/tokens-fetch.mjs 是整个 token 流水线的核心,它用 Style Dictionary v5(style-dictionary 5.4.4)配合@tokens-studio/sd-transforms完成编译。几个关键实现细节值得注意:
- 拉取来源:
tokens:fetch直接从 GitHub raw 地址https://raw.githubusercontent.com/NangoHQ/nango/design/tokens/packages/design-system/tokens/tokens.json拉取最新tokens.json; --build-only模式:tokens:build从本地已有的tokens.json读取,不访问网络;若tokens.json不存在会直接报错并提示先运行tokens:fetch;--strict模式:脚本支持--strict参数,遇到未解析的 token 别名(SD 会保留为原始{alias}字符串)时直接抛错,而不是静默跳过,避免产出无效 CSS;- boxShadow 序列化:SD v5 的 DTCG token 把处理后的值放在
$value中,内置css/variables格式对 boxShadow 数组会退化为"[object Object]",因此脚本自定义了formatTokenValue,把innerShadow/outerShadow的x/y/blur/spread/color逐项拼成 CSSbox-shadow字符串; - 原子化写入:脚本先构建 CSS,若 Style Dictionary 抛错则保持磁盘上的
tokens.json原封不动,保证工作区始终处于“旧 tokens.json + 旧 tokens.generated.css”的一致状态。
从脚本的PRIM_MAPPINGS常量可以看出原始 token 到 Tailwind 命名空间的映射策略(这也是生成 CSS 中各类工具类的前缀来源):
| 原始 token 前缀 | CSS 变量 | Tailwind 工具类 |
|---|---|---|
radius- | --ds-radius-* | rounded-ds-* |
border-width- | --ds-border-width-* | border-ds-* |
typography-font-size- | --ds-typography-font-size-* | text-ds-* |
typography-font-weight- | --ds-typography-font-weight-* | font-ds-* |
typography-line-height- | --ds-typography-line-height-* | leading-ds-* |
typography-letter-spacing- | --ds-typography-letter-spacing-* | tracking-ds-* |
间距(spacing)被刻意排除在映射之外:Tailwind 默认的 4px 间距刻度与--ds-space-*token 完全一致,所以gap-2就等于--ds-space-2(8px),无需额外注册。
3.5 权威方向(Canonical direction)
Token 变更应当始终发源于 Figma——设计师是事实源的拥有者。直接编辑tokens.json并推送到design/tokens在技术上可行(GitHub sync 是双向的),但这不是预期工作流,且存在与 Figma 文件产生分叉的风险。
3.6 生成的 CSS 结构
tokens.generated.css由五段内容组成(真实文件见 packages/design-system/tokens/tokens.generated.css):
/* Primitives — --ds- 前缀,原始刻度值 */ :root { --ds-color-neutral-50: #f9fafb; --ds-radius-sm: 4px; --ds-typography-font-size-md: 14px; ... } /* Semantic tokens — light (默认) */ :root { --surface-canvas: #f9fafb; --text-strong: #111827; ... } /* Semantic tokens — dark */ [data-theme="dark"] { --surface-canvas: #0f172a; --text-strong: #f8fafc; ... } /* Tailwind @theme — 语义 token → bg-*、text-*、border-*、shadow-* 工具类 */ @theme { --color-surface-canvas: var(--surface-canvas); --shadow-focus-outline-default: var(--focus-outline-default); ... } /* Tailwind @theme — 原始 token → rounded-ds-*、text-ds-*、font-ds-* 等 */ @theme { --radius-ds-sm: var(--ds-radius-sm); --text-ds-md: var(--ds-typography-font-size-md); --font-weight-ds-medium: var(--ds-typography-font-weight-medium); ... }对照真实文件可以看到完整的分层:原始 token 段(--ds-*前缀)包含颜色(neutral/brand/info/success/warning/danger 全色阶、alpha 透明度、icon stroke)、间距、圆角、边框宽度、排版、动效时长与缓动曲线、阴影、透明度、模糊、布局断点等;语义 token 段按 light(:root)与 dark([data-theme="dark"])分别定义--icon-*、--surface-*、--text-*、--border-*、--interactive-*、--status-*、--chart-*、--focus-ring-*等;随后是@theme注册段与.type-*复合排版类(例如.type-heading-lg一次性设置 font-family、font-size、font-weight、line-height、letter-spacing)。
关于 boxShadow 的注册有一个细节:语义阴影 token 使用@theme inline注册,使生成的工具类直接引用语义变量(如var(--focus-outline-default)),而不是经过中间--shadow-*CSS 属性,从而保证暗色主题对底层语义变量的覆盖始终生效。
3.7 Token 命名与工具类映射
| Token | CSS 变量 | Tailwind 工具类 |
|---|---|---|
Primitives.color.neutral.50 | --ds-color-neutral-50 | — |
Primitives.radius.sm | --ds-radius-sm | rounded-ds-sm |
Primitives.typography.font-size.md | --ds-typography-font-size-md | text-ds-md |
Primitives.typography.font-weight.medium | --ds-typography-font-weight-medium | font-ds-medium |
Semantic/Light.surface.canvas | --surface-canvas | bg-surface-canvas |
Semantic/Light.text.strong | --text-strong | text-text-strong |
Semantic/Light.focus-outline-default | --focus-outline-default | shadow-focus-outline-default |
原始颜色 token(--ds-color-*)被刻意排除在@theme之外,目的是强制组件只使用语义 token;而原始尺寸/排版 token 则以ds-前缀进入@theme,使它们可以作为普通工具类使用,无需[var(...)]这种任意值写法。
3.8 在 webapp 中消费
packages/webapp/src/index.css通过包名直接引入生成的 CSS:
@import '@nangohq/design-system/tokens/tokens.generated.css';同时 packages/webapp/package.json 以"@nangohq/design-system": "file:../design-system"的形式本地引用该包。搜索packages/webapp可以看到大量组件消费示例:providers.tsx引入TooltipProvider,components/patterns/下的CriticalErrorAlert.tsx、DestructiveActionModal.tsx、SecretInput.tsx、ScopesInput.tsx、EditableInput.tsx、FilterMultiSelect.tsx、KeyValueInput.tsx、PeriodSelector.tsx、ConditionalTooltip.tsx等模式组件都从@nangohq/design-system导入Button、IconButton、Badge、Alert、InputGroup、Tooltip等。
四、组件:按需添加,遵循统一模式
组件按需添加——只有当产品页面真正需要时才新增,不做投机性添加(AGENTS.md中明确写明)。添加新组件的完整指南见 packages/design-system/AGENTS.md。
4.1 消费方式与约束
import { Button, IconButton, buttonVariants } from '@nangohq/design-system';消费者还必须在应用根部导入一次 token CSS(见 3.8 节)。组件只使用语义 CSS 变量——不允许出现裸 hex 色值、硬编码尺寸或硬编码间距。
消费方应用不能直接给组件套className/style做外观定制(有 lint 规则拦截),因此当某个页面需要现有 props/变体覆盖不到的外观时,修复点就在本包内:新增或扩展一个变体。动手前应先与设计师确认该变体确实缺失,并在Figma 设计系统中同步添加对应变体,保持代码与设计一致(详见stories/StylingAndCustomization.mdx)。
4.2 以 Button 为参照的组件模式
packages/design-system/src/components/ui/button.tsx 是仓库中的参照实现,完整展示了这套模式:
- 导出
cva变体对象,供消费者组合复用(buttonVariants); - 导出 props 接口,继承
React.HTMLAttributes与VariantProps<typeof variants>; forwardRef,保证消费方可以挂 ref;asChild(基于@radix-ui/react-slot的Slot),支持把组件渲染为链接、路由<Link>等任意元素;cn()合并类名:cn(variants(...), className)。
buttonVariants定义了 8 个变体——primary、secondary、outline、ghost、danger,以及三个链接类变体link-accent、link-danger、link-neutral——每个变体都严格走“Figma token → CSS 变量 → Tailwind 类”的映射,例如:
primary: [ 'bg-interactive-primary text-text-on-accent border-transparent', 'hover:bg-interactive-primary-hover', 'active:bg-interactive-primary-active', 'disabled:bg-interactive-disabled disabled:text-text-disabled disabled:border-transparent', 'focus-visible:shadow-focus-outline-default' ]尺寸变体覆盖2xs(20px 方形图标按钮,配合IconButton)到lg;Button还支持loading属性,加载时渲染内部Spinner并隐藏行内图标,同时设置aria-busy;IconButton则要求必传label作为可访问名称(同时应用为aria-label与title)。
新增组件时,可以用 shadcn CLI 生成基础文件:
cd packages/design-system npx shadcn@latest add <component-name>生成的文件落在src/components/ui/<component-name>.tsx,但其中使用的 shadcn 默认 CSS 变量(bg-primary、text-muted-foreground等)在本包中不存在,必须替换为tokens/tokens.generated.css中的 token 工具类或var(--token-name)。
4.3 用 token 工具类还是原生 Tailwind?
规则一句话总结:外观(appearance)——设计师拥有决策权的值——必须来自 DS token 工具类;布局、尺寸与动效使用原生 Tailwind,其默认刻度与 token 一一对应。
设计师指定的值用ds-*/ 语义工具类:
| 类别 | 用法 | 示例 |
|---|---|---|
| 颜色 | 语义工具类 | bg-surface-canvas、text-text-default、border-border-default |
| 焦点环 | shadow-*语义类 | shadow-focus-outline-default |
| 字号 | text-ds-* | text-ds-md而非text-sm |
| 字重 | font-ds-* | font-ds-medium而非font-medium |
| 行高 | leading-ds-* | leading-ds-normal而非leading-normal |
| 字距 | tracking-ds-* | tracking-ds-tight而非tracking-tight |
| 圆角 | rounded-ds-* | rounded-ds-sm而非rounded |
| 边框宽度 | border-ds-* | border-ds-1而非border |
允许直接使用原生 Tailwind 的类别(默认刻度与 token 相等,无需 DS 工具类):
| 类别 | 用法 | 示例 |
|---|---|---|
| 间距 | 原生刻度 | gap-2、px-3(对应--ds-space-*4px 刻度) |
| 图标尺寸 | 原生刻度 | size-4(16px)、size-3.5(14px) |
| 动效 | 原生刻度 | duration-100=--ds-motion-duration-fast;ease-in-out=--ds-motion-easing-standard |
| 布局/结构 | 原生 | flex、grid、items-center、relative、truncate、z-10等 |
禁止在className中以[var(--ds-*)]任意值形式内联 token——应优先使用已注册的工具类或上述原生类;若某个 token 没有对应工具类,则把它加进@theme块以生成工具类,而不是退回var()。
另外有一个已知陷阱:border-ds-*工具类只设置四边边框,方向形式(border-t-ds-1、border-b-ds-hairline)在 Tailwind v4 下不会渲染(根因未解)。单边边框的正确做法是:1px 用原生border-t等加颜色工具类(如border-t border-border-default);0.5px hairline 用任意值border-t-[0.5px]。
4.4 焦点环(Focus rings)
组件通过box-shadow应用焦点环,使用--focus-outline-default(破坏性操作使用--focus-outline-danger),并自行用focus-visible:outline-none抑制原生 outline:
'focus-visible:outline-none focus-visible:shadow-focus-outline-default' // 破坏性操作: 'focus-visible:outline-none focus-visible:shadow-focus-outline-danger'不需要全局*:focus-visiblereset——webapp 通过其focus-default工具类在组件级别作用相同的行为。真实文件中的语义阴影变量(--focus-outline-default、--focus-outline-danger、--container-inset等)都定义在tokens.generated.css的语义段中。
4.5cn()类名合并助手
所有类名合并都经过 packages/design-system/src/lib/cn.ts 的cn():它把clsx(条件/数组语法)与tailwind-merge(冲突消解)组合起来,例如cn('h-8', 'h-10')会得到'h-10'而不是'h-8 h-10'。其中dsTwMergeConfig(定义于 packages/design-system/src/lib/twMergeConfig.ts,并从 barrel 导出)教会tailwind-merge识别本包@theme生成的text-ds-*、border-ds-*等 token 工具类,使它们被归入正确的冲突组、正确地去重——消费方可以在自己的cn中复用同一份配置。
4.6 新增组件五步法(来自 AGENTS.md)
- shadcn CLI 生成基础:
npx shadcn@latest add <component-name>,写入src/components/ui/。注意components.json的@/*别名在 tsconfig 中刻意缺失(会与 Storybook 的@→ webapp 别名冲突),脚手架期间可临时添加、完成后移除; - 替换 shadcn CSS 变量为设计 token:所有颜色/圆角/间距/动效必须来自
tokens.generated.css; - 遵循组件模式:
cva变体对象 +forwardRef+asChild+cn()+ 导出variants; - 添加 Storybook 故事:与组件同目录的
<name>.stories.tsx,标题为Design System/Components/<ComponentName>,展示所有需要“冻结”的变体与状态(默认、hover、disabled);focus 状态无需专门 story,评审者直接在默认/交互故事中点击即可触发; - 从 barrel 导出:在
src/index.ts追加export { MyComponent, type MyProps, myVariants } from './components/ui/my-component';。
4.7 暗色模式
token 文件同时定义了亮色与暗色两套值:亮色是:root,暗色是[data-theme="dark"]。组件不需要暗色变体——CSS 变量自动完成主题切换。因此在 Storybook 中用Themes按钮切换后,务必在两种主题下逐一检查每个组件。
五、Tokens Studio GitHub sync 配置
5.1 配置项
| 字段 | 值 |
|---|---|
| 仓库(Repository) | NangoHQ/nango |
| 分支(Branch) | design/tokens |
| 文件路径(File path) | packages/design-system/tokens/tokens.json |
5.2 Figma 插件首次设置
- 安装 Tokens Studio for Figma 插件;
- 插件应能检测到已有 GitHub sync 并预填除 access token 外的所有字段;
- 对于Personal Access Token字段,从 1Password 获取:
- Vault(保险库):Eng
- Item(条目):GitHub PAT - Figma Tokens Studio Sync
- 点击Save——插件会从
design/tokens分支加载既有 token。
六、工程实践小结
- 设计单一事实源在 Figma,代码侧以
tokens.json提交入库、tokens.generated.css由流水线产出并提交,二者缺一不可; - 同步纪律:
npm run tokens:fetch拉取 + 重建,npm run tokens:build仅重建;合并冲突后优先用tokens:build; - 组件纪律:只使用语义 token 工具类,禁止硬编码外观值;新外观通过新增变体解决,且需与 Figma 设计系统同步;
- Storybook 是验收现场:每个组件都要有 story,每个 story 都要过 axe 审计,PR 级联的托管 Storybook 便于分享 WIP 组件;
- AI 友好:通过
@storybook/addon-mcp启用 MCP 后,AI 助手可以直接查询组件文档与 props,降低跨包开发的上下文成本。
这套设计系统以"Figma 出 token、Style Dictionary 编译、Tailwind v4 生成工具类、Radix + CVA + tailwind-merge 产出组件、Storybook 统一验收"的闭环,为 Nango 的 Web 应用(packages/webapp)提供了跨页面一致的外观基础。需要深入某一环节时,建议直接阅读 packages/design-system/AGENTS.md(组件新增全指南)、packages/design-system/scripts/tokens-fetch.mjs(token 流水线源码)与 packages/design-system/tokens/tokens.generated.css(token 全量清单)。
【免费下载链接】nangoBuild product integrations with AI.项目地址: https://gitcode.com/GitHub_Trending/na/nango
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考