mcpField
W
web-ui-shadcn-ui

web-ui-shadcn-ui

github.com/agents-inc设计与 UI2686 次安装2 天前更新
代码仓库

关于 web-ui-shadcn-ui

面向使用 shadcn/ui 构建界面的开发者,说明如何通过 CLI 添加并维护组件,以及如何编辑组件源码。指南还涉及主题变量、样式组合、表单布局和复合组件。

主要功能

  1. 01通过 CLI 添加及检查组件
  2. 02编辑自有组件源码与变体
  3. 03使用 OKLCH 变量配置主题
  4. 04通过 cn() 合并样式类
  5. 05使用 Field 组织表单布局

适用场景

  1. 01将注册表组件加入项目
  2. 02为组件增加样式变体或行为
  3. 03配置浅色与深色主题
  4. 04组合对话框、标签页及命令菜单

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-ui-shadcn-ui

安装后在 Claude Code 中输入/web-ui-shadcn-ui确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
command-palette.md
composition.md
core.md
data-table.md
dialogs.md
forms.md
theming.md

详细介绍

shadcn/ui 组件模式

快速指南: shadcn/ui 是一种分发方式,而非依赖项——npx shadcn@latest add 会将组件源码写入你的代码库,从此由你维护。由此带来三点影响,决定了其他一切:自定义意味着编辑文件,而不是从外部覆盖;升级时需要审核差异,而不是提升版本号;组件本身由基础组件库和实用 CSS 组合而成,源码会按名称导入它们。使用 OKLCH 中的 CSS 自定义属性设置主题,通过 cn() 合并类名,并使用 Field 布局字段。Field 已取代与表单库耦合的 Form/FormField 模式。

详细资源:


适用路径

  • 组件基于哪种基础组件库。 init --base radix 和 --base base 会为同一注册表条目生成不同的组件源码。无论哪种方式,shadcn 层级的 API——导出名称、cn()、变体 props、data-slot——都相同,因此本指南中的内容始终适用;不同之处在于,编辑源码时需要使用哪个基础组件的 props。
  • 使用 Field 还是旧版 Form。 新工作使用 Field 及其同类组件,它们仅用于布局。已使用 Form/FormField/FormItem/FormControl/FormMessage 的代码库仍可正常工作;这些组件绑定到一种表单库,不应再用于新增字段。两种模式均见 examples/forms.md。

<critical_requirements>

编写 shadcn/ui 代码前

使用 npx shadcn@latest add <name> 添加组件。 CLI 会解析注册表条目的依赖项,安装所需的基础包,将文件写入 components.json 记录的路径,并将导入重写为你配置的别名——从文档中粘贴源码时,这些操作都不会发生。

更改组件行为前,先阅读组件源码。 它是你仓库中的文件,而不是一个包的边界,因此对于“我能否更改它”,答案始终是“可以”,值得问的问题是还有哪些地方导入了它。

所有类都通过 cn() 处理,并将传入的 className 放在最后。 它通过保留最后一个 Tailwind 工具类来解决冲突,这样调用方的 px-8 就会替换组件的 px-4,而不是与之并存,陷入优先级平局。

每种新增背景色都要配上前景色。 如果有 --brand 却没有 --brand-foreground,该表面上的文本就会继承之前的颜色,通常会在一种主题下通过检查,却在另一种主题下对比度不足。请在 :root 和 .dark 中都添加这两个变量,并添加将它们转换为工具类的 @theme inline 映射。

优先使用变体,而不是一次性类。 如果不止一个调用方需要某种样式,就应该将其加入组件的变体映射,在其中命名并定义类型;在各个调用处重复一组类名,意味着每次都要重新做出相同的决定。

</critical_requirements>


自动检测: shadcn/ui、shadcn、components.json、npx shadcn、shadcn@latest add、cn()、data-slot、@theme inline、--base radix、--base base、Field、FieldLabel、FieldDescription、FieldError、FieldGroup、FieldSet、FieldLegend、CommandDialog、SheetContent、AlertDialogAction、--primary-foreground、--sidebar-*、--chart-*

适用范围:

  • 将组件添加到项目并保持更新——CLI、components.json、差异
  • 编辑自有组件源码:添加变体、属性或行为
  • 主题契约——背景色/前景色配对、OKLCH 值、@theme inline、暗色模式
  • 组合注册表提供的复合组件:Card、Dialog、Sheet、Tabs、Command、Field
  • 为特定交互场景选择注册表中功能重叠的组件

其他内容另行处理:

  • 工具类编写和 CSS 流水线——本指南确定组件读取哪些变量;如何配置和构建工具层则是另一个主题
  • 底层基础库——其属性、asChild 语义和无障碍契约,取决于项目所选的基础库
  • 变体映射编写通法——本指南介绍如何扩展组件源码中已有的变体映射
  • 表单状态、验证和提交——Field 负责布局和无障碍连接,不保存值

shadcn/ui 颠覆了通常的交易方式。组件库提供 API 并保留源码;而这里提供源码,除此之外一无保留。注册表只是一个起点,文件一旦落入你的项目,它就不再是上游版本。

这就是 CLI 比看起来更重要的原因。它不只是一个方便的复制粘贴封装——只有它了解注册表条目的依赖关系、你的别名配置以及你选择的基础库;也正是它让 --diff 日后能够告诉你,你的副本与注册表版本相差多远。

组件由什么构成,在这里被视为组合中的既定事实。 CLI 写入的源码会导入一个基础库、应用工具类并声明变体映射——不提及这些内容的指南,就无法描述读者正在查看的文件。讲解这些组成部分是另一项工作,已在上方的其他内容另行处理中说明。


<decision_framework>

选择覆盖层

Confirmation the user must answer?
└─ AlertDialog — no dismiss path except Cancel or Action

Form or detailed content?
├─ Wide viewport  → Dialog (centred)
└─ Narrow viewport → Drawer (rises from the bottom)
   Both at once → branch on a media query; see examples/composition.md

Editing in context, page still visible?
└─ Sheet — slides in from an edge

A short action or a single selection?
└─ Popover for content, DropdownMenu for a list of actions

选择字段控件

Text            → Input, or Textarea when it wraps
Two to five options, all worth showing → RadioGroup
Many options    → Select, or Combobox when it needs filtering
Several at once → Checkbox per option
A setting that takes effect immediately → Switch
An agreement or a term to accept        → Checkbox
A date          → Calendar, or DatePicker with a trigger

Whatever the control, wrap it in Field — not the legacy FormField.

</decision_framework>


核心模式

模式 1:安装和检查组件

CLI 是与注册表交互的接口。--dry-run、--diff 和 --view 都能在不写入任何内容的情况下解答有关组件的问题,因此可以据此检查升级。

npx shadcn@latest init                  # writes components.json
npx shadcn@latest add button card dialog
npx shadcn@latest add button --diff     # how far your copy has drifted
npx shadcn@latest info                  # the resolved project context

完整命令列表:reference.md

模式 2:主题约定

每种颜色都是一对——一个表面色及其上显示的文本颜色——在 :root 中声明,在 .dark 中覆盖,并通过 @theme inline 暴露为实用类。新增颜色需要包含这三项,否则它只会存在于两种主题之一,或者不会出现在任何实用类中。

:root {
  --brand: oklch(0.627 0.265 303.9);
  --brand-foreground: oklch(1 0 0);
}

@theme inline {
  --color-brand: var(--brand);
  --color-brand-foreground: var(--brand-foreground);
}

--brand-foreground 是用于 --brand 之上 的文本颜色,而不是品牌色文本——这种命名方式与大多数约定正好相反,弄错会导致文本不可见。

完整代码:examples/theming.md

模式 3:cn() 以及顺序为何重要

cn() 用于解决冲突,而不是拼接。来自同一组的两个实用类会合并为最后一个,因此传入的 className 放在最后,调用方的覆盖值才会生效。

cn("px-4", "px-8"); // → "px-8", not "px-4 px-8"

<div className={cn("rounded-lg border bg-card shadow-sm", className)} />;

直接拼接字符串会让两个类都保留在列表中,此时哪个类生效取决于它们在生成的样式表中的顺序,而非调用位置。

完整代码:examples/core.md

模式 4:通过变体映射扩展组件

每个随包提供的组件都通过变体映射声明样式——由基础类列表以及命名的 variant 和 size 轴组成,属性类型则根据该映射推导。你可以在自己的源代码中编辑该映射来添加变体;不需要增强 API,因为没有这个必要。

variants: {
  variant: {
    default: "bg-primary text-primary-foreground hover:bg-primary/90",
    brand: "bg-brand text-brand-foreground hover:bg-brand/90",  // added
  },
}

<Button variant="brand">Subscribe</Button>;

添加行为属性也是同样的做法——请参阅 examples/composition.md 中的加载按钮。

模式 5:使用 Field 布局表单

Field 及其同系列组件包含标签、描述、错误插槽以及它们之间的 ARIA 关联,且自身不持有任何值。因此它们可以与任何表单库、服务端操作搭配使用,也可以完全独立使用。

<Field data-invalid={hasError}>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" aria-invalid={hasError} />
  <FieldDescription>We will never share your email.</FieldDescription>
  {hasError && <FieldError errors={errors} />}
</Field>

约定包括以下三点:Field 上的 data-invalid、控件上的 aria-invalid,以及仅在存在错误时才渲染 FieldError。FieldGroup、FieldSet 和 FieldLegend 用于对字段分组;orientation 用于切换标签位置。

完整代码:examples/forms.md

模式 6:组合复合组件

Card、Dialog、Sheet、Tabs 和 Command 都由一组部件组成。通过包装这些部件来构建特定组件;将部件替换为普通元素会失去样式钩子,而对于覆盖层,还会失去其行为。

function ProductCard({ title, price }: ProductCardProps) {
  return (
    <Card>
      <CardHeader>
        <CardTitle>{title}</CardTitle>
        <CardDescription>${price}</CardDescription>
      </CardHeader>
    </Card>
  );
}

触发器上的 asChild 会将其合并到你的子元素上,而不是嵌套在子元素内部,因此用 Button 包装链接时,不会渲染出按钮内嵌套锚点的情况。

完整代码:examples/dialogs.md、examples/command-palette.md


<red_flags>

容易出错的地方

运行时会出问题:

  • 没有 components.json — 所有 CLI 命令都会失败,因为别名、样式和基础原语都记录在其中
  • 添加背景色却没有对应的 -foreground 配对色 — 该表面上的文本会继承颜色,结果通常在一种主题下正常,在另一种主题下对比度不足
  • 将颜色添加到 :root,却未添加到 .dark,或未映射到 @theme inline — 该颜色只存在于一种主题中,或者没有对应的工具类
  • 对已经是 oklch(...) 的值再套用 hsl() — 会得到无效颜色,因此声明会被丢弃,元素会回退到继承的颜色
  • Button 中包含链接,而不是通过 asChild 将其应用到链接上 — 在按钮中嵌套锚点属于无效 HTML,键盘用户会将其体验为一个令人困惑的控件
  • 为 Select 设置了 onValueChange,却既未提供 value,也未提供 defaultValue — 它永远不会显示选中项

容易令人意外的行为:

  • --primary-foreground 是 --primary 上的文本颜色,不是主色文本
  • 拼接类名字符串而不是使用 cn(),会让相互冲突的工具类同时存在,因此最终生效的是哪一个取决于样式表顺序,而不是调用位置
  • 编辑 components/ui/ 中的组件是预期工作流;之后可通过 --diff 查看你对照注册表所做的更改
  • 在 React 19 下,随附组件将 ref 作为普通属性接收,并通过 data-slot 标记其组成部分,而不是导出可供定位的类名
  • 读取系统偏好的主题提供程序需要在 <html> 上设置 suppressHydrationWarning,因为服务器无法知道客户端会应用哪个类
  • Form/FormField/FormItem/FormControl/FormMessage 仍然可用,也仍绑定到一个表单库 — 新字段应使用 Field
  • 图表颜色会直接读取为 var(--chart-1);旧配置使用的 hsl() 包装现在是错误的,而不只是多余

评论

支持 Markdown