Why Choose Shadcn in 2025?

Shadcn 解决什么问题?为什么它是 2025 最好用的前端 UI 方案?

前言

在 2024/4 写过:「什么是 Shadcn UI?为什么在前端圈这么火爆?」文章,不过如今回顾感觉写得烂透了,正巧最近又在导入 Vue Shadcn,就来更齐全的回顾这项工具。

如果你在考虑建立基于 Tailwind 的组件库这篇文章值得一看。

前端环境背景

前端开发者们都逐渐接纳 Utility-First 的 CSS 解决方案,如同 CSS Survey🔗 趋势,其中最大宗使用的 CSS 框架就是 TailwindCSS🔗,但它只解决管理「如何撰写与管理 CSS」这件事,也就是透过生成事先定义好的 Utility CSS Class 来快速开发与管理。

UI 扩展难题

相较于传统 CSS 框架如 Bootstrap🔗,Tailwind 并不在乎如何实践具体的样式,如:按钮、输入框、弹窗……等,虽然给予了极大的弹性和尽可能少量的抽象,但也意味着要自己打造样式。

另一方面传统组件库面临过于死板(也可以说是统一),难以客制化的问题,像是 Element+🔗,会需要直接选取对应的元素套用样式,如果维护时习惯不好可能会爆出一堆 !important 互相覆盖,最后导致整个项目难以改动。

::v-deep .el-input {
width: 300px;
border: none;
.el-input__wrapper {
background-color: transparent !important;
border: none;
.el-input__inner {
color: skyblue;
}
}
}

还有一种方式是从源头修改并编译 Sass 如 Bootstrap🔗

Custom.scss
// Option B: Include parts of Bootstrap
// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
@import "../node_modules/bootstrap/scss/functions";
// 2. Include any default variable overrides here
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/variables-dark";
// 4. Include any default map overrides here
// 5. Include remainder of required parts
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root";
// 6. Include any other optional stylesheet partials as desired; list below is not inclusive of all available stylesheets
@import "../node_modules/bootstrap/scss/utilities";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
@import "../node_modules/bootstrap/scss/helpers";
// ...
// 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`
@import "../node_modules/bootstrap/scss/utilities/api";
// 8. Add additional custom code here

会提到传统的 UI 库,是因为遇过的案例是为了保留 Tailwind 的弹性,但又期望现成组件库快速开发,混在一起用就大爆炸难维护。

问题是如何保持组件弹性?

Tailwind 自然有现成 UI 组件方案如:Flowbite🔗Daisy UI🔗,而其中我认为最合理顺手的解决是 Shadcn🔗

  1. Shadcn 是一款基于 Radix UI🔗 的组件合集。
  2. Radix UI 著重在提供无障碍、无装饰且开放🔗的通用组件,使用原生 CSS 和 React 建构。
  3. Shadcn🔗 在 Radix 之上封装添加合适的 Tailwind 样式,即拿即用。

也就是说 Radix 实践了绝大多数功能,而 Shadcn 包装出了可用的外观,实现「功能」与「样式」的分离。

Shadcn 不是一般的组件库

UI 组件库通常是安装某个 NPM 套件并且引用于你的项目当中,但 Shadcn 并不是「组件库」而是「组件集」。

意味着它实际上就是一段组件代码集合,本质就是透过复制粘贴组件代码来使用,也可以用 CLI 工具🔗来达成,不过背后还是相同的动作。这样的设计提供更大的样式修改弹性与透明度,甚至能赋予 AI 更好更全面的前后文,但也意味着负担更大的责任,具体来说像是组件是分散的,其中使用的依赖套件与维护要自行追踪。

CLI 透过特定的 registry.json🔗 格式来决定要安装的组件描述、位置、种类与依赖,不一定要使用它们官方组件的 registry,也可以自架🔗,实际上就是一个特定的 JSON 格式装载组件描述。

Shadcn 初始化

透过 CLI 初始化 Shadcn🔗 时会将所有设置与偏好记录于新生成的 components.json 文件如下:

Terminal window
pnpm dlx shadcn@latest init
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "new-york",
"typescript": true,
"tailwind": {
"config": "",
"css": "src/style.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"composables": "@/composables",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib"
},
"iconLibrary": "lucide"
}

所有的变量都会初始化在 Tailwind 设置当中,透过 CSS 原生变量存储,原则很简单,分成「背景」与「前景色」,并且有数种语义化用途的颜色如:primarysecondarymuteddestructive

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}

Shadcn 实际案例

举例 Shadcn Vue 提供 Tabs🔗 组件,可以透过文件中的链接:

了解整个组件的全貌,就可以下 CLI 指令把相关的组件与依赖都安装上:

Terminal window
pnpm dlx shadcn-vue@latest add tabs

实际上背后会抓 官方的 registry🔗

{
"files": [
{
"name": "tabs",
"type": "registry:ui",
"dependencies": [
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [],
"files": [
{
"path": "ui/tabs/Tabs.vue",
"type": "registry:ui"
},
{
"path": "ui/tabs/TabsContent.vue",
"type": "registry:ui"
},
{
"path": "ui/tabs/TabsList.vue",
"type": "registry:ui"
},
{
"path": "ui/tabs/TabsTrigger.vue",
"type": "registry:ui"
},
{
"path": "ui/tabs/index.ts",
"type": "registry:ui"
}
]
},
]
}

CLI 默认就会安装所有依赖与复制粘贴所有组件🔗,假设真的不满意 Shadcn 提供的样式,大可以自创一个 Shadcn 组件如 WindowTab🔗,并复制一份 Tabs 修改,不用担心影响到原先的 Tabs。

Tabs/
├── Tabs.vue
├── TabsContent.vue
├── TabsList.vue
├── TabsTrigger.vue
└── index.ts
WindowTab.vue
<script setup lang="ts">
import { Tabs, TabsContent, TabsList, TabsTrigger } from './Tabs'
</script>

更多面向的 Shadcn

可以到 Awesome Shadcn UI🔗 查看相关的仓库,不管是延伸的工具、组件集或不同前端框架的版本都有。像是 Shadcn Vue🔗 是基于 Reka UI🔗,但整体概念是相同的。

如果要与设计端配合,也有现成 Figma 组件与设计系统可以留意:

延伸阅读