前言
在 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 元件與設計系統可以留意: