 的十条实战规则)
SurfSense 的 shadcn/ui 样式规范语义色、内置变体与 cn() 的十条实战规则【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSenseSurfSense 的前端Next.js React 应用surfsense_web基于 shadcn/ui 组件体系构建仓库内置了一份面向开发者和 AI 协作者Cursor Skill的样式规则文档 styling.md。本篇围绕该文档逐条展开十条样式约定——语义色优先、状态指示器禁用裸色值、变体优先于 className 覆盖、gap替代space-*、size-*/truncate简写、禁用手动dark:覆写、cn()条件类名合并、以及浮层组件禁用手动 z-index——并结合仓库中的主题配置globals.css、components.json、tailwind.config.js与cn()工具函数实现lib/utils.ts说明每条规则在 SurfSense 中的底层支撑与落地方式。规则一语义色优先Semantic colors规则要求组件中禁止直接使用具体色阶如bg-blue-500必须使用语义化 token// 错误直接写死 Tailwind 色阶 div classNamebg-blue-500 text-white p classNametext-gray-600Secondary text/p /div// 正确使用语义 token div classNamebg-primary text-primary-foreground p classNametext-muted-foregroundSecondary text/p /div这条规则的可行性来自 SurfSense 的主题变量体系。globals.css 中在:root浅色与.dark深色作用域下定义了全套 OKLCH 格式的颜色变量--background/--foreground、--primary/--primary-foreground、--muted/--muted-foreground、--destructive、--border、--input、--ring、--chart-1至--chart-5以及 sidebar 系列变量。文件中的theme inline块再将它们注册为 Tailwind v4 的--color-*主题变量从而让bg-primary、text-muted-foreground等工具类真正指向 CSS 变量而非固定色值。从源码结构看语义色与name/name-foreground的成对约定一致基础变量用于背景-foreground用于该背景之上的文字与图标。这也是为什么次级文本必须写text-muted-foreground而不是text-gray-600——后者在深色模式下不会随.dark变量切换而自动适配。规则二状态/正负指示器禁用裸色值规则明确表示成功、失败或状态的指示器应使用Badge变体、text-destructive一类语义 token或自定义 CSS 变量而不是text-emerald-600这类原始 Tailwind 颜色// 错误 span classNametext-emerald-60020.1%/span span classNametext-green-500Active/span span classNametext-red-600-3.2%/span// 正确 Badge variantsecondary20.1%/Badge BadgeActive/Badge span classNametext-destructive-3.2%/span仓库中确有此类实践例如运行状态徽标 run-status-badge.tsx 就通过Badge的variant属性表达不同状态而非手写颜色。规则同时指出若需要一个尚不存在的成功/正向色应使用 Badge 变体或按照 customization.md 的流程在主题中添加自定义 CSS 变量——这保持了颜色只从主题来的单一来源原则。规则三优先使用内置变体Built-in variants first当视觉需求恰好与组件内置变体重合时禁止用 className 拼出同样的效果// 错误手写复现 outline 变体 Button classNameborder border-input bg-transparent hover:bg-accent Click me /Button// 正确 Button variantoutlineClick me/Button这一点在 SurfSense 的 shadcn 配置中得到了印证components.json 声明style: new-york、cssVariables: trueUI 组件统一落在/components/ui别名即 components/ui/ 目录下。这些组件普遍基于cvaclass-variance-authority定义variant与size维度因此能用变体解决的绝不手动叠类名是对 cva 体系的尊重——手写类名不仅冗长还会与组件内部的样式约定产生冲突风险且无法享受后续npx shadcn add更新时的合并逻辑。规则四className 只用于布局颜色定制走三步法className的合法用途是布局max-w-md、mx-auto、mt-4等不是用来覆写组件颜色或字体的// 错误用 className 覆写 Card 的颜色与字重 Card classNamebg-blue-100 text-blue-900 font-bold CardContentDashboard/CardContent /Card// 正确className 只做布局 Card classNamemax-w-md mx-auto CardContentDashboard/CardContent /Card原文档给出定制组件外观的优先级顺序customization.md 在此基础上补充为完整四步内置变体——variantoutline、variantdestructive等语义色 token——bg-primary、text-muted-foregroundCSS 变量——在全局 CSS 中定义自定义颜色SurfSense 即是在 globals.css 中额外定义了--panel、--rail、--brand、--highlight等项目级 token并通过theme inline注册为--color-panel等工具类新增变体或包装组件——直接编辑组件源码添加 cva 变体或组合 shadcn 原语封装出更高层组件如ConfirmDialog包装AlertDialog系列。这一顺序的价值在于前两步零成本且随主题自动适配明暗模式第三、四步将定制沉淀进主题或组件层使颜色决策与具体业务代码解耦。规则五至七Tailwind 实用类简写约定三条针对类名冗余的硬性约束1. 禁用space-x-*/space-y-*一律使用gap-*。替换关系为space-y-4→flex flex-col gap-4space-x-2→flex gap-2div classNameflex flex-col gap-4 Input / Input / ButtonSubmit/Button /divspace-*通过子元素相邻选择器实现遇到隐藏/条件渲染的子节点会产生空隙计算异常gap由 flex/grid 容器原生保证行为更可预测。2. 宽高相等时优先size-*。例如图标、头像、骨架屏等场景写size-10而非w-10 h-10减少重复且避免两个维度被分开修改导致不一致。3. 优先truncate简写。单行文本截断统一写truncate不展开为overflow-hidden text-ellipsis whitespace-nowrap三个类名。这三条本质上是同一原则用最少的类名表达完整的语义意图降低样式审计成本——从源码结构看SurfSense 的components/ui目录下数十个组件文件正是按这种单类名、高密度的风格编写的。规则八禁止手动dark:颜色覆写深色模式不需要组件级dark:bg-gray-950这类手动覆写语义 token 本身就随 CSS 变量切换// 正确靠 token 自动切换 div classNamebg-background text-foregroundSurfSense 的明暗切换机制是 class 策略globals.css 通过custom-variant dark (:is(.dark *))声明.dark类驱动的深色变体同时在:root与.dark下分别给出两套 OKLCH 变量值配套的 tailwind.config.js 中也显式声明darkMode: [class]该 v3 风格配置文件与 v4 主样式并存可视为历史配置与当前主配置的叠加。customization.md 建议 Next.js 项目使用next-themes的ThemeProvider并设置attributeclass与上述机制对应。由此推导出的工程含义是bg-white dark:bg-gray-950这类写法不仅冗余还会破坏改一个变量全站换肤的能力——组件一旦写死具体色值就脱离了主题变量体系。规则九条件类名统一使用 cn()禁止在 className 中手写模板字符串三元表达式必须使用项目提供的cn()工具// 错误手写三元 div className{flex items-center ${isActive ? bg-primary text-primary-foreground : bg-muted}}// 正确 import { cn } from /lib/utils div className{cn(flex items-center, isActive ? bg-primary text-primary-foreground : bg-muted)}/lib/utils即 surfsense_web/lib/utils.ts与 components.json 中utils: /lib/utils别名一致其实现是clsxtailwind-merge的组合import { type ClassValue, clsx } from clsx; import { twMerge } from tailwind-merge; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }这个实现带来两个手写三元无法提供的能力一是clsx支持条件、数组、对象等多种输入形态表达力远超布尔三元二是tailwind-merge会按 Tailwind 的语义解析并去重冲突类如p-2 p-4保留后者这让 shadcn 组件默认样式 外部 className 覆写的用法天然安全——父组件传入的布局类与组件内部类合并时冲突项按后者优先规则收敛而不是产生两个互相打架的类名。规则十浮层组件禁止手动 z-indexDialog、Sheet、Drawer、AlertDialog、DropdownMenu、Popover、Tooltip、HoverCard这些浮层组件自行管理叠放层级永远不要给它们追加z-50或z-[999]。对应到仓库即 components/ui/ 下的dialog.tsx、sheet.tsx、drawer.tsx、alert-dialog.tsx、dropdown-menu.tsx、popover.tsx等文件。从源码结构看这些组件基于 Radix UI 原语构建层级由 Radix 的 portal 挂载与内部 z-index 约定统一控制在业务侧追加任意 z-index 会打破浮层之间的叠放契约例如 Dialog 内嵌套 Popover 的遮挡顺序且无法随上游组件更新保持正确。与主题定制文档的关系颜色从哪来、如何演进styling.md 的十条规则回答组件里怎么写样式而 customization.md 回答颜色体系本身怎么维护二者构成完整闭环工作原理CSS 变量:root浅色 /.dark深色→ Tailwind 工具类bg-primary等→ 组件引用工具类。改变变量即改变所有引用它的组件。SurfSense 的 globals.css 正是这一链路的实例变量定义、theme inline注册、layer base中的border-border/bg-background基线样式三者齐备。自定义颜色新增语义色应写入全局 CSS而不是新建 CSS 文件在:root/.dark各给一份值再注册到 Tailwindv4 用theme inlinev3 用tailwind.config.js的extend.colors之后即可像内置 token 一样使用例如bg-warning text-warning-foreground。圆角体系--radius全局控制圆角rounded-lg var(--radius)、rounded-md calc(var(--radius) - 2px)等派生关系在 tailwind.config.js 的borderRadius扩展与 globals.css 的theme inline中均有对应。组件更新检查npx shadcnlatest add button --diff可在升级组件前预览差异避免升级把定制样式冲掉。小结这套规范在 SurfSense 中的工程价值十条规则看似琐碎实际收敛了同一个问题——业务组件代码中不应出现颜色决策和层级决策。颜色只允许来自主题变量体系globals.css外观差异只允许通过 cva 变体或新增变体表达条件类名统一经cn()合并lib/utils.ts浮层叠放交给 Radix 原语。其结果是换肤只需改变量、明暗模式零成本、组件升级可安全合并。对维护 SurfSense 前端或为其贡献代码时直接对照 styling.md 的 Incorrect/Correct 示例即可快速自查一份 PR 的样式合规性。【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考