Learn More →
GitHub - https://github.com/shadcn-ui/ui
Shadcn UI 是基於 Tailwind CSS 底層封裝 Radix UI 的 React UI 元件集合,能夠支援 Next.js, Astro, Remix, Gatsby 等框架。
shadcn-ui/ui 專案於 2023 年 1 月發布到 GitHub,截至目前(2024 年 7 月)已有超過 65K 星星數,榮登 2023 JavaScript Rising Stars 榜首。
之所以能夠成為炙手可熱的開源專案,如 Shadcn 官方文件介紹所述:
Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source. (您可以複製貼上設計精美的元件至應用程式中。無障礙、可客製化且開源。)
Shadcn UI 並不是一個 component library(元件庫),而是可重用元件的「集合」。換言之,Shadcn UI 並不會以 dependency 的形式出現在 pacakge.json
,使用者可以直接把原始碼複製貼上到專案中,也能自行修改客製化。
Shadcn UI 在前端生態圈崛起的原因,可從過去我們所熟悉的 UI Framework,如 Bootstrap, Meterial UI, Ant Design 等說起。
透過元件庫提供的預設元件進行開發,就不需要從零開始造輪子,能夠統一介面樣式並實現 RWD 等需求,然而在實際使用元件庫時,會發現在客製化上有一定限制,經常會為了改一小部分樣式,而透過 !important
、:deep
等「魔改」的方式,來達到自訂義樣式需求,如此不只增加程式碼的複雜性,也不易後續維護使用。
如上所述,Shadcn UI 具備的種種特性,提供現有的元件庫解決方案,其優點如下:
shadcn-ui
引入使用詳細步驟可參考官方文件,本篇以 Next.js 專案為例:
creat-next-app
指令建置 Next.js 專案npx create-next-app@latest my-app --typescript --tailwind --eslint
shadcn-ui
初始專案cd my-app
npx shadcn-ui@latest init
components.json
做調整Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Do you want to use CSS variables for colors? › no / yes
建立好的 Next.js 專案架構可參考如下,其中引入的 Shadcn 元件會放在 compoent/ui
資料夾底下:
.
├── app
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── ui // 引入的元件可在專案中進行編輯
│ │ ├── alert-dialog.tsx
│ │ ├── button.tsx
│ │ ├── dropdown-menu.tsx
│ │ └── ...
│ ├── main-nav.tsx // 將引入的元件進一步客製化元件
│ ├── page-header.tsx
│ ├── alert.tsx
│ ├── sidebar.tsx
│ └── ...
├── lib
│ └── utils.ts
├── styles // 自定義樣式
│ └── globals.css
├── next.config.js
├── package.json
├── postcss.config.js
├── tailwind.config.js // Tailwind 設定檔
└── tsconfig.json
import type { Metadata } from "next";
import "./globals.css";
import { Inter as FontSans } from "next/font/google"
import { cn } from "@/lib/utils"
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
})
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={cn(
"min-h-screen bg-background font-sans antialiased",
fontSans.variable
)}>
{children}
</body>
</html>
);
}
tailwind.config.js
調整設定檔 theme.extend.fontFamily
,即可根據需求自定義樣式
const { fontFamily } = require("tailwindcss/defaultTheme")
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"],
theme: {
extend: {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
},
},
},
}
npx shadcn-ui@latest add button
安裝好的元件路徑會在 components 底下:@components/ui/button
// app/page.tsx
import { Button } from "@/components/ui/button";
export default function Home() {
return (
<>
<h1>Hello World</h1>
<Button>Enter!</Button>
</>
)
}
components/ui/button.tsx
檔案中新增自訂義樣式 newButton
,即可在頁面使用
// app/page.tsx
import { Button } from "@/components/ui/button";
export default function Home() {
return (
<>
<h1>Hello World</h1>
<Button variant="outline">outline</Button>
<Button variant="link">link</Button>
<Button variant="newButton">newButton</Button>
</>
)
}
結果如下:
由於 Shadcn UI 所有元件已內建 Dark Mode 樣式設定,因此只需搭配 Themes 來實作,而 Next.js 可搭配 next-themes
套件,詳細可參考 Dark mode 官網範例:
next-themes
npm install next-themes
next-themes
中的 ThemeProvider 來管理主題樣式:components/theme-provider.tsx
// components/theme-provider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }: RootLayoutProps) {
return (
<>
<html lang="en" suppressHydrationWarning>
<head />
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
</>
)
}
radix-ui/react-icons
引入 icon 使用npm install @radix-ui/react-icons
use client
才能引入 useTheme
hook 使用
"use client"
import * as React from "react"
import { useTheme } from "next-themes"
import { MoonIcon, SunIcon } from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button";
export default function Home() {
const { theme, setTheme } = useTheme();
return (
<>
<h1>Hello World</h1>
<Button variant="outline">outline</Button>
<Button variant="link">link</Button>
<Button variant="newButton">newButton</Button>
<div>
<Button variant="outline" size="icon" onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</div>
</>
)
}
效果如下:
前陣子在開發 Next.js 專案時,是搭配使用 material-ui 這套 UI Framework,卻意外發現在過程中遇到許多痛點:
因此藉此機會來研究 Shadcn UI,希望能改善開發上遇到的問題,後續再根據專案需求來導入合適的方案,畢竟任何工具沒有絕對好壞,而是根據使用情境來評估是否導入使用。