@shadcn/lint六大核心规则一次讲透:从no-restyle到no-unknown-classes全解析
【免费下载链接】lintAn agent-first linter for Tailwind design systems. Write design system rules that agents can verify.项目地址: https://gitcode.com/gh_mirrors/lint3/lint
@shadcn/lint 是一个面向 Tailwind 设计系统的 Lint 工具,它帮你把"哪些样式能改、哪些颜色能用"变成机器可检查的规则。当 AI 编码代理或团队成员写错样式时,它不仅能报错,还会告诉你该用哪个变体、哪个主题色来替代——这正是它被称为 "agent-first linter" 的原因。本文用一篇带你吃透它的6 大核心规则。
一、为什么需要 @shadcn/lint?🎯
用 TypeScript 类型也能限制style属性,但类型报错只说"不行",不说"该怎么做"。而 @shadcn/lint 的报错自带基于你组件库的修复建议:列出可用的 size、variant、主题色 token 和定义它们的文件路径。
它的核心特点:
- ✅不改组件 API:规则写在 Lint 配置里,组件代码保持灵活
- ✅无需 shadcn/ui:自己的 Tailwind 组件和主题就能用
- ✅多框架支持:React、Vue、Svelte,ESLint 和 Oxlint 双引擎
- ✅官方实测:在 150+ 次任务跑测中,AI 代理基本一轮纠错即可清零违规,成本降低 10%~48%
二、六大规则速览 📋
| 规则 | 拦截什么 | 一句话定位 |
|---|---|---|
no-restyle | 通过className重设计系统组件 | 外观归组件,布局归页面 |
no-raw-colors | bg-pink-500等裸色板色、未声明 token、SVG 硬编码色 | 颜色必须走主题 |
no-arbitrary-values | p-[13px]、bg-[#333]等任意值 | 值必须落在标尺上 |
no-inline-styles | style内联属性、<style>元素 | 样式必须走 class |
no-unknown-classes | rounded-huge等 Tailwind 生成不了 CSS 的类名 | 拼写和存在性兜底 |
require-static-classes | `bg-${color}`这类 linter 读不懂的类名 | 保证其他规则"看得见" |
每条规则的完整文档都在 docs/rules/ 目录下,规则共享的allow/deny/contracts/message选项说明见 docs/rules.md。
三、no-restyle:设计系统组件的"外观锁" 🔒
这是最核心的一条规则。设计原则是:外观用组件变体,布局才交给className。
它怎么工作?规则把你的类名分成 8 个类别:layout(布局)、color(颜色)、typography(排版)、spacing(间距)、shape(形状)、effects(特效)、motion(动效)和unclassified(无法识别)。以allow: ["layout"]为例,mt-4、w-full放行,而p-4、bg-pink-500、rounded-full全部报错——且报错会列出组件的 sizes 和 variants。
三个进阶玩法:
- Contracts(契约):给不同组件定不同规则,比如允许
CardTitle改排版、允许CardContent改间距,颜色谁都不能动 - deny 精准排除:布局整体放行但单独禁掉
w-* - 自定义文案:报错信息可写成 "Use a Button size: sm, lg",直接告诉代理该用什么
它能穿透 import、re-export 和转发className的包装组件,所以<SaveButton className="p-4">一样会被识别为对 Button 的重设计。
📄 详见:docs/rules/no-restyle.md
四、no-raw-colors:颜色只认主题 🎨
这条规则拦截三类"野生颜色":
- 色板裸色:
bg-pink-500、text-amber-500 - 未声明 token:主题里没有
highlight,写bg-highlight照样报错 - SVG 硬编码色:
fill="#ec4899"、stroke="red"这类字面量
报错时会列出主题已声明的 token、给出附近颜色的建议,并指明主题文件在哪。white、black、transparent、currentColor这类通用名直接放行。
新手常见的疑问:为什么@utility tap-target自定义工具类不报错?因为它属于你自己的词表;而.text-danger { color: #f00 }这种普通选择器不会被当 token,背后的颜色仍是裸色,照样会被抓。
📄 详见:docs/rules/no-raw-colors.md
五、no-arbitrary-values:值必须落在标尺上 📏
p-[13px]、rounded-[10px]、bg-[#333]是设计系统的大敌——它们绕过了你的 spacing 标尺和色彩 token。
这条规则的亮点是能给出精确替代:默认--spacing为 4px 时,p-[13px]会提示Use "p-3.25" instead (same value, on the scale),而且变体、负值、important 标记都会保留。
注意两个边界:
data-[state=open]:flex、bg-(--brand)这类任意变体和变量简写不算任意值,直接放行- 布局类任意值可用
allow: ["layout"]放行,比如侧边栏的w-[320px]往往是合理的
📄 详见:docs/rules/no-arbitrary-values.md
六、no-inline-styles:样式请走 class 通道 🚫
组件里写style={{ color: "red" }}或塞一个<style>元素,都会让设计系统形同虚设。这条规则的检查相当细腻:
- ✅放行:用 CSS 自定义属性传递动态值(
style={{ "--panel-width": \${width}px` }}`)是官方推荐姿势 - ❌报错:普通内联属性、自定义属性里的硬编码色值(
"--label-color": "#ec4899")、读不懂的 style 对象、<style>元素 - 🔍会顺藤摸瓜:同文件的
const colors = { accent: "#ec4899" }再引用colors.accent,一样会被查出来
例外按CSS 属性名配置(如动画库的transform),注意它不接收 Tailwind 类名。
📄 详见:docs/rules/no-inline-styles.md
七、no-unknown-classes:拼写错误的最后防线 🔍
rounded-huge、flex-cols、hovr:flex——这些类名 Tailwind 根本生成不了 CSS,页面"看起来能跑"其实没生效。这条规则直接调用你项目里安装的 Tailwind v4 + 主题 + 自定义工具类 + 插件来做存在性校验:
- 拼写接近时给出纠错建议:
flex-cols→ "Did you mean 'flex-col'?",编辑器里可一键替换 - 主题里的
@utility自定义工具和 CSS 类选择器都被识别,不误报 - 外部样式表提供的类(如
editor-root)通过allow加白名单
官方建议先从warn级别启用,边加白名单边收严。
📄 详见:docs/rules/no-unknown-classes.md
八、require-static-classes:让其他规则"看得见" 👀
前五条规则都有一个前提:类名要可读。如果你写了className={`bg-${color}`},linter 根本无法检查,等于规则失效。这条规则专门拦截"读不懂的类名":
- ✅ 放行:静态字符串、完整类名间的三元选择、
cn("mt-4", wide && "w-full") - ❌ 报错:模板拼接、导入的未知变量、未知函数调用
它是整个体系的"地基":读不懂的类名交给它报,读懂的部分继续由其他规则把关。官方建议在组件目录内关闭它(组件自己调用 variant 函数属于正常写法)。
📄 详见:docs/rules/require-static-classes.md
九、六条规则如何协同作战 🤝
单独使用当然可以,但组合起来才是完整防线:
| 场景 | 谁负责 |
|---|---|
<Button className="p-4">不该改间距 | no-restyle |
<Button className="p-[13px]">值不在标尺上 | no-arbitrary-values |
<Button className="bg-pink-500">用了裸色 | no-raw-colors |
<div className="flex-cols">类名不存在 | no-unknown-classes |
style={{ color: "red" }}内联样式 | no-inline-styles |
`bg-${color}`读不懂 | require-static-classes |
⚠️ 两条容易踩的联动细节:
- 白名单互不通用:某条规则的
allow只对它自己生效,no-restyle放行p-*不会放过no-arbitrary-values对p-[13px]的检查 - 组件目录记得关规则:
no-restyle、no-arbitrary-values、require-static-classes要在组件目录内关闭,否则组件自己给自己定样式也会报错;而颜色类和存在性规则建议在组件目录保持开启
完整的渐进式接入路径(先 warn 后 error、逐条加规则)见 docs/adoption.md,规则背后的工作原理见 docs/how-it-works.md。
十、新手快速上手路径 🚀
- 装包:Node.js 20.19+,
npm install -D @shadcn/lint eslint @typescript-eslint/parser(Oxlint 用户装oxlint即可) - 配最小规则集:先开
no-arbitrary-values,熟悉后再逐条加,完整步骤见 SETUP.md - 让 AI 代理跑 lint:在
AGENTS.md里加一句"改动后运行npm run lint并修复所有错误",规则的价值立刻翻倍 - shadcn/ui 项目零配置:
components.json会自动发现组件目录和主题;自建组件用settings.shadcn指定ui前缀即可
规则的具体实现代码在 packages/lint/src/rules/ 下,每条规则一个文件,想深挖某个规则的边界行为,读源码 + 对应测试是最高效的方式。
一句话总结:no-restyle管"能不能改",no-raw-colors和no-arbitrary-values管"值对不对",no-unknown-classes和no-inline-styles管"写没写对",require-static-classes管"看不看得见"。六条规则各司其职,你的设计系统从此有了机器可执行的契约。
【免费下载链接】lintAn agent-first linter for Tailwind design systems. Write design system rules that agents can verify.项目地址: https://gitcode.com/gh_mirrors/lint3/lint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考