当前位置:首页>java>组件就选 shadcn - 网站代码怎么改 06/10

组件就选 shadcn - 网站代码怎么改 06/10

  • 2026-10-21 13:29:40
组件就选 shadcn - 网站代码怎么改 06/10

shadcn 是 Web 出海圈最常见的起步方案了,各种 AI 工具即便不用 NextJS,也会选择 shadcn + Tailwind。独立开发社区,似乎看不到有人在讨论 Material UI,AntD。甚至 Tailwind 最佳排档 Daisy UI 聊的人也少了。

之前百花齐放、各有千秋的组件库,现在收敛到一个默认选择。shadcn 是怎么做到的呢?

想要在一个群体中出类拔萃,得有独树一帜并且行之有效的哲学。UI 组件库的问题就是同质化严重,只靠更多的样式和组件,很难建立起长期优势。

这时候就需要挖一下底层。之前的各种 React 组件设计系统,本质上都是设计一套工作方式和审美,我们来使用。但很快或者因为业务需求,或者单纯审美疲劳,人们就想要加点东西。然后就发现,这事不简单!

而 shadcn 的底层假设是“用户一定不想重复造轮子;用户也总有一天想要改组件”。所以设计哲学从一开始就是“把代码设计得很容易改”。

shadcn 做了以下几个事:

  1. 1. 代码交给你。有专门工具负责导入组件代码;导入进来这几小段代码和 shadcn 没关系,不会因为哪一天 shadcn 升级就用不了了(代码里根本没有import X from shadcn这种东西)
  2. 2. 使用科学和现代的方式设计层级:基本的 ARIA 行为模式(选择、点击、聚焦等等)交给 Radix/Base UI 这样的 headless “原语”组件(primitives),视觉样式交给 tailwind,组件变体通过 CVA(Class Variance Authority) 方式来定义(而不是通过 if-else 的方式写在 JS 的逻辑里)。

在这个分层设计下,我们要改一个组件——

  1. 1. 绝大多数情况下,只需要在引用 shadcn 组件的时候,传入不同的 className 就可以改变外观(颜色、间距、圆角大小、动画效果)
  2. 2. 如果要修改或者增加属性,在 variants 里增删 key(比如 buttonVariants)
  3. 3. 结构调整(修改组件的 DOM 结构),改组件的 JSX 代码
  4. 4. 键盘行为(修改组件的键盘交互逻辑),改对应的 Base/Radix 组件属性(这些也都包含在 shadcn 组件代码里)

具体细节——

  1. 1. 通过 Tailwind 控制外观,是什么意思?

组件的所有外观都能通过传入Tailwind className 来覆盖,比如想要一个圆形按钮,不管按钮默认是个什么样,只需要写:

1

<Button className="rounded-full" />

出来就一定是个圆的。

  1. 2. 通过 CVA 实现 API 的自定义,是什么意思?

传统的 UI 组件库这样定义按钮:

1

<Button type="primary" size="large" danger ghost />

想要增加一个可以全局使用的按钮属性,最快的方法是基于这个 Button 重新封装一个新的,然后在页面到处使用这个<NewButton>。

但 shadcn 的组件定义是透明的(这里是一个简化的 button)——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

// components/ui/button.tsximport { cva } from "class-variance-authority"const buttonVariants = cva(  "inline-flex items-center justify-center rounded-md",  {    variants: {      variant: {        default: "bg-primary text-primary-foreground",        outline: "border border-input",      },      size: {        default: "h-10 px-4 py-2",        sm: "h-9 rounded-md px-3",      },    },    defaultVariants: {      variant: "default",      size: "default",    },  })function Button({ variant, size, className, ...props }) {  return (    <button      className={cn(buttonVariants({ variant, size }), className)}      {...props}    />  )}export {Button, buttonVariants};

想要把 variant 改成更直观的“色调”属性?只需要改两处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// buttonVariantsvariants: {    tone: {        primary: "bg-blue-600 text-white hover:bg-blue-700",        danger: "bg-red-600 text-white hover:bg-red-700"      },    //size}// Buttonexport function Button({ tone, size, className, ...props }: ButtonProps) {  return (    <button      className={cn(buttonVariants({ tone, size }), className)}      {...props}    />  )}

之后就可以在项目的任何地方可以使用这个新的属性了:<Button tone="primary" size="sm" />

  1. 3. 能够方便改组件的内部结构是什么意思?

一个带结构的 shadcn 按钮可能长这样:

1
2
3
4
5
6
7
8
9

export function  StructuredButton({ leftIcon, rightIcon, ...props }) {  return (    <button>      {leftIcon}      <span>{props.children}</span>      {rightIcon}    </button>  )}

因为代码就在 /components/ui 下,要改它的结构直接改就行,比如去掉leftIcon。

  1. 4. 键盘行为调整是什么意思?

这个可能很少用到。

比如有一个对话框,删除某个图片时需要用户确认,这里会涉及一些 Radix/Base UI 给提供的“默认键盘行为”(ARIA 交互模式):

  • • 打开时 focus 是落在第一个 focusable 元素
  • • 按 Tab 会在 Dialog 内循环
  • • 按 Esc 会关闭 Dialog
  • • 按回车效果等同于点击当前 focus 的按钮

——我们的组件可以自然带有这些我们熟悉的行为,就是因为 shadcn 建立在 Radix 组件基础上。

现在假设弹窗的第一个按钮是“确定”,第二个是“取消”。如果这是个高危操作,而我们不希望用户手滑给确认了、还回头骂我们,一个办法是默认 focus 的元素在“取消”按钮上——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

"use client"import * as React from "react"import {  Dialog,  DialogContent,  DialogDescription,  DialogFooter,  DialogHeader,  DialogTitle,} from "@/components/ui/dialog"import { Button } from "@/components/ui/button"export function DangerConfirmDialog({ open, onOpenChange }) {  const cancelRef = React.useRef<HTMLButtonElement | null>(null)  return (    <Dialog open={open} onOpenChange={onOpenChange}>      <DialogContent        onOpenAutoFocus={(e) => {          e.preventDefault()          cancelRef.current?.focus()        }}      >        <DialogHeader>          <DialogTitle>删除确认</DialogTitle>          <DialogDescription>          是否确认?          </DialogDescription>        </DialogHeader>        <DialogFooter>          <Button ref={cancelRef} tone="primary">            取消          </Button>          <Button tone="danger">            确认          </Button>        </DialogFooter>      </DialogContent>    </Dialog>  )}

在常规的组件库里(比如 AntD),自动聚焦这种底层逻辑是隐藏的,即便能改也需要通过特定的 API。shadcn 直接把onOpenAutoFocus 暴露出来,我们(或者 AI)改起来当然容易很多。


除了上面这些总体设计,在一些复杂的组件的底层实现上,shadcn 的选择也能体现作者很高的选型标准。比如  data table 就是以设计精良的 TanStack table 为基础实现的。

在看 shadcn 时,总能想到曾经每天用的 Python web 框架 FastAPI。它的作者也是靠对各种先进工具的灵活组合,使得 FastAPI 兼具最强性能和一流开发者体验。真是既好看又实用啊。这需要有代码能力,对工具链的理解,还有大师级的代码审美。

最新文章

随机文章