前言
在 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。
// 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。
- Shadcn 是一款基于 Radix UI 的组件合集。
- Radix UI 著重在提供无障碍、无装饰且开放的通用组件,使用原生 CSS 和 React 建构。
- Shadcn 在 Radix 之上封装添加合适的 Tailwind 样式,即拿即用。
也就是说 Radix 实践了绝大多数功能,而 Shadcn 包装出了可用的外观,实现「功能」与「样式」的分离。
Shadcn 不是一般的组件库
UI 组件库通常是安装某个 NPM 套件并且引用于你的项目当中,但 Shadcn 并不是「组件库」而是「组件集」。
意味着它实际上就是一段组件代码集合,本质就是透过复制粘贴组件代码来使用,也可以用 CLI 工具来达成,不过背后还是相同的动作。这样的设计提供更大的样式修改弹性与透明度,甚至能赋予 AI 更好更全面的前后文,但也意味着负担更大的责任,具体来说像是组件是分散的,其中使用的依赖套件与维护要自行追踪。
CLI 透过特定的 registry.json 格式来决定要安装的组件描述、位置、种类与依赖,不一定要使用它们官方组件的 registry,也可以自架,实际上就是一个特定的 JSON 格式装载组件描述。
Shadcn 初始化
透过 CLI 初始化 Shadcn 时会将所有设置与偏好记录于新生成的 components.json
文件如下:
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 原生变量存储,原则很简单,分成「背景」与「前景色」,并且有数种语义化用途的颜色如:primary
、secondary
、muted
、destructive
。
@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 组件,可以透过文件中的链接:
- Component Source:查看组件原始码。
- API Reference:查看 Shadcn 底层使用 Reka 组件接口格式。
了解整个组件的全貌,就可以下 CLI 指令把相关的组件与依赖都安装上:
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.tsWindowTab.vue
<script setup lang="ts">import { Tabs, TabsContent, TabsList, TabsTrigger } from './Tabs'</script>
更多面向的 Shadcn
可以到 Awesome Shadcn UI 查看相关的仓库,不管是延伸的工具、组件集或不同前端框架的版本都有。像是 Shadcn Vue 是基于 Reka UI,但整体概念是相同的。
如果要与设计端配合,也有现成 Figma 组件与设计系统可以留意: