Appearance
Tailwind组件库(shadcn-ui-daisyUI)
概述
虽然Tailwind CSS提供了强大的工具类,但从零开始构建完整的UI组件仍然需要大量时间。幸运的是,社区提供了许多基于Tailwind的组件库,其中shadcn/ui和DaisyUI是最受欢迎的两个。本文将深入探讨这两个组件库的使用方法、特点对比和最佳实践。
shadcn/ui
什么是shadcn/ui
shadcn/ui不是传统意义上的组件库,而是一个可复制粘贴的组件集合。它提供了精美设计的组件代码,你可以直接复制到项目中并完全拥有代码控制权。
安装和配置
bash
# 使用CLI初始化
npx shadcn-ui@latest init
# 回答配置问题
# ✔ Would you like to use TypeScript (recommended)? yes
# ✔ Which style would you like to use? › Default
# ✔ Which color would you like to use as base color? › Slate
# ✔ Where is your global CSS file? › src/index.css
# ✔ Would you like to use CSS variables for colors? › yes
# ✔ Where is your tailwind.config.js located? › tailwind.config.js
# ✔ Configure the import alias for components: › @/components
# ✔ Configure the import alias for utils: › @/lib/utilstypescript
// 生成的配置文件
// components.json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}
// src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}添加组件
bash
# 添加单个组件
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add dialog
# 添加多个组件
npx shadcn-ui@latest add button card dialog
# 列出所有可用组件
npx shadcn-ui@latest add核心组件使用
tsx
// Button组件
import { Button } from "@/components/ui/button"
function ButtonExample() {
return (
<div className="flex gap-4">
<Button>Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon">🔍</Button>
</div>
)
}
// Card组件
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
function CardExample() {
return (
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
)
}
// Dialog组件
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
} from "@/components/ui/dialog"
function DialogExample() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
// Form组件
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const formSchema = z.object({
username: z.string().min(2).max(50),
email: z.string().email(),
})
function FormExample() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Enter username" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
// Table组件
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
function TableExample() {
const data = [
{ id: 1, name: "John", email: "john@example.com" },
{ id: 2, name: "Jane", email: "jane@example.com" },
]
return (
<Table>
<TableCaption>A list of users</TableCaption>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
// Toast通知
import { useToast } from "@/components/ui/use-toast"
import { ToastAction } from "@/components/ui/toast"
function ToastExample() {
const { toast } = useToast()
return (
<Button
onClick={() => {
toast({
title: "Scheduled: Catch up",
description: "Friday, February 10, 2023 at 5:57 PM",
action: (
<ToastAction altText="Undo">Undo</ToastAction>
),
})
}}
>
Show Toast
</Button>
)
}自定义主题
typescript
// lib/themes.ts
export const themes = {
zinc: {
light: {
background: "0 0% 100%",
foreground: "240 10% 3.9%",
primary: "240 5.9% 10%",
// ...
},
dark: {
background: "240 10% 3.9%",
foreground: "0 0% 98%",
primary: "0 0% 98%",
// ...
},
},
rose: {
light: {
background: "0 0% 100%",
foreground: "240 10% 3.9%",
primary: "346.8 77.2% 49.8%",
// ...
},
dark: {
background: "20 14.3% 4.1%",
foreground: "0 0% 95%",
primary: "346.8 77.2% 49.8%",
// ...
},
},
}
// 应用主题
function applyTheme(theme: keyof typeof themes, mode: 'light' | 'dark') {
const colors = themes[theme][mode]
const root = document.documentElement
Object.entries(colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value)
})
}DaisyUI
什么是DaisyUI
DaisyUI是一个基于Tailwind CSS的组件库,提供了预设计的组件类名。与shadcn/ui不同,DaisyUI作为Tailwind插件安装,组件通过类名使用。
安装和配置
bash
# 安装DaisyUI
npm install -D daisyui@latest
# 或使用特定主题
npm install -D daisyui@latest @tailwindcss/typographyjavascript
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [
require("daisyui")
],
daisyui: {
themes: [
"light",
"dark",
"cupcake",
"bumblebee",
"emerald",
"corporate",
"synthwave",
"retro",
"cyberpunk",
"valentine",
"halloween",
"garden",
"forest",
"aqua",
"lofi",
"pastel",
"fantasy",
"wireframe",
"black",
"luxury",
"dracula",
"cmyk",
"autumn",
"business",
"acid",
"lemonade",
"night",
"coffee",
"winter",
],
darkTheme: "dark",
base: true,
styled: true,
utils: true,
rtl: false,
prefix: "",
logs: true,
},
}核心组件
jsx
// Button组件
function DaisyButtonExample() {
return (
<div className="flex gap-2">
<button className="btn">Default</button>
<button className="btn btn-primary">Primary</button>
<button className="btn btn-secondary">Secondary</button>
<button className="btn btn-accent">Accent</button>
<button className="btn btn-ghost">Ghost</button>
<button className="btn btn-link">Link</button>
<button className="btn btn-sm">Small</button>
<button className="btn btn-lg">Large</button>
<button className="btn btn-outline">Outline</button>
<button className="btn btn-outline btn-primary">Outline Primary</button>
<button className="btn btn-square">
<svg>...</svg>
</button>
<button className="btn btn-circle">
<svg>...</svg>
</button>
<button className="btn loading">Loading</button>
<button className="btn" disabled>Disabled</button>
</div>
)
}
// Card组件
function DaisyCardExample() {
return (
<div className="card w-96 bg-base-100 shadow-xl">
<figure><img src="/image.jpg" alt="Album" /></figure>
<div className="card-body">
<h2 className="card-title">
Card Title
<div className="badge badge-secondary">NEW</div>
</h2>
<p>Card description goes here</p>
<div className="card-actions justify-end">
<div className="badge badge-outline">Fashion</div>
<div className="badge badge-outline">Products</div>
</div>
</div>
</div>
)
}
// Modal组件
function DaisyModalExample() {
return (
<>
<label htmlFor="my-modal" className="btn">Open Modal</label>
<input type="checkbox" id="my-modal" className="modal-toggle" />
<div className="modal">
<div className="modal-box">
<h3 className="font-bold text-lg">Modal Title</h3>
<p className="py-4">Modal content goes here</p>
<div className="modal-action">
<label htmlFor="my-modal" className="btn">Close</label>
</div>
</div>
</div>
</>
)
}
// Form组件
function DaisyFormExample() {
return (
<div className="form-control w-full max-w-xs">
<label className="label">
<span className="label-text">Email</span>
<span className="label-text-alt">Alt label</span>
</label>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
/>
<label className="label">
<span className="label-text-alt">Helper text</span>
<span className="label-text-alt">Alt label</span>
</label>
</div>
)
}
// Alert组件
function DaisyAlertExample() {
return (
<div className="space-y-4">
<div className="alert alert-info">
<svg>...</svg>
<span>Info alert</span>
</div>
<div className="alert alert-success">
<svg>...</svg>
<span>Success alert</span>
</div>
<div className="alert alert-warning">
<svg>...</svg>
<span>Warning alert</span>
</div>
<div className="alert alert-error">
<svg>...</svg>
<span>Error alert</span>
</div>
</div>
)
}
// Navbar组件
function DaisyNavbarExample() {
return (
<div className="navbar bg-base-100">
<div className="flex-1">
<a className="btn btn-ghost normal-case text-xl">daisyUI</a>
</div>
<div className="flex-none">
<ul className="menu menu-horizontal px-1">
<li><a>Link</a></li>
<li>
<details>
<summary>Parent</summary>
<ul className="p-2 bg-base-100">
<li><a>Submenu 1</a></li>
<li><a>Submenu 2</a></li>
</ul>
</details>
</li>
</ul>
</div>
</div>
)
}
// Drawer组件
function DaisyDrawerExample() {
return (
<div className="drawer">
<input id="my-drawer" type="checkbox" className="drawer-toggle" />
<div className="drawer-content">
<label htmlFor="my-drawer" className="btn btn-primary drawer-button">
Open drawer
</label>
</div>
<div className="drawer-side">
<label htmlFor="my-drawer" className="drawer-overlay"></label>
<ul className="menu p-4 w-80 min-h-full bg-base-200 text-base-content">
<li><a>Sidebar Item 1</a></li>
<li><a>Sidebar Item 2</a></li>
</ul>
</div>
</div>
)
}
// Table组件
function DaisyTableExample() {
return (
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Job</th>
<th>Favorite Color</th>
</tr>
</thead>
<tbody>
<tr>
<th>1</th>
<td>Cy Ganderton</td>
<td>Quality Control Specialist</td>
<td>Blue</td>
</tr>
<tr className="hover">
<th>2</th>
<td>Hart Hagerty</td>
<td>Desktop Support Technician</td>
<td>Purple</td>
</tr>
<tr>
<th>3</th>
<td>Brice Swyre</td>
<td>Tax Accountant</td>
<td>Red</td>
</tr>
</tbody>
</table>
</div>
)
}主题系统
javascript
// 使用预设主题
module.exports = {
daisyui: {
themes: ["light", "dark", "cupcake"],
},
}
// 自定义主题
module.exports = {
daisyui: {
themes: [
{
mytheme: {
"primary": "#3b82f6",
"secondary": "#f000b8",
"accent": "#37cdbe",
"neutral": "#3d4451",
"base-100": "#ffffff",
"info": "#3abff8",
"success": "#36d399",
"warning": "#fbbd23",
"error": "#f87272",
},
},
"dark",
"cupcake",
],
},
}
// 在React中切换主题
function ThemeSwitch() {
const [theme, setTheme] = React.useState('light')
React.useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
}, [theme])
return (
<select
className="select select-bordered"
value={theme}
onChange={(e) => setTheme(e.target.value)}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="cupcake">Cupcake</option>
<option value="mytheme">My Theme</option>
</select>
)
}
// 使用主题颜色
function ThemeColorExample() {
return (
<div className="bg-primary text-primary-content p-4">
Primary themed content
</div>
)
}对比与选择
shadcn/ui vs DaisyUI
typescript
// 特性对比
interface Comparison {
feature: string;
shadcn: string;
daisyui: string;
}
const comparison: Comparison[] = [
{
feature: "安装方式",
shadcn: "CLI复制组件到项目",
daisyui: "npm包作为Tailwind插件"
},
{
feature: "代码控制",
shadcn: "完全控制组件代码",
daisyui: "通过配置和类名控制"
},
{
feature: "自定义程度",
shadcn: "极高,可修改任何代码",
daisyui: "中等,通过主题和CSS变量"
},
{
feature: "类型安全",
shadcn: "TypeScript原生支持",
daisyui: "通过类名,无类型提示"
},
{
feature: "组件数量",
shadcn: "30+核心组件",
daisyui: "50+组件"
},
{
feature: "主题系统",
shadcn: "CSS变量,需手动实现",
daisyui: "内置29个主题"
},
{
feature: "文件大小",
shadcn: "只包含使用的组件",
daisyui: "包含所有组件样式"
},
{
feature: "学习曲线",
shadcn: "需要理解组件实现",
daisyui: "简单,记住类名即可"
},
{
feature: "适用场景",
shadcn: "需要高度定制的项目",
daisyui: "快速原型和标准UI"
}
]选择建议
typescript
// 选择shadcn/ui的情况
const useShadcn = {
scenarios: [
"需要完全控制组件代码",
"TypeScript项目",
"需要高度自定义的UI",
"团队熟悉React和Tailwind",
"长期维护的企业项目",
"需要无障碍访问优化",
"使用Radix UI等headless UI库"
],
example: `
// shadcn/ui适合这样的场景
import { Button } from "@/components/ui/button"
// 可以直接修改组件源码
export function CustomButton() {
return (
<Button
className="custom-animation"
// 添加自定义props
>
Click me
</Button>
)
}
`
}
// 选择DaisyUI的情况
const useDaisyUI = {
scenarios: [
"快速开发原型",
"需要大量预设组件",
"喜欢使用类名的开发方式",
"需要多个现成主题",
"团队成员Tailwind经验丰富",
"内容驱动的网站",
"不需要深度定制"
],
example: `
// DaisyUI适合这样的场景
function QuickPrototype() {
return (
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">快速原型</h2>
<p>使用预设类名快速构建</p>
<div className="card-actions">
<button className="btn btn-primary">Action</button>
</div>
</div>
</div>
)
}
`
}实战案例
使用shadcn/ui构建Dashboard
tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Overview } from "@/components/overview"
import { RecentSales } from "@/components/recent-sales"
export function Dashboard() {
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-between space-y-2">
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
</div>
<Tabs defaultValue="overview" className="space-y-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Revenue
</CardTitle>
<svg>...</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">$45,231.89</div>
<p className="text-xs text-muted-foreground">
+20.1% from last month
</p>
</CardContent>
</Card>
{/* 更多统计卡片 */}
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<Card className="col-span-4">
<CardHeader>
<CardTitle>Overview</CardTitle>
</CardHeader>
<CardContent className="pl-2">
<Overview />
</CardContent>
</Card>
<Card className="col-span-3">
<CardHeader>
<CardTitle>Recent Sales</CardTitle>
</CardHeader>
<CardContent>
<RecentSales />
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div>
)
}使用DaisyUI构建电商页面
jsx
function EcommercePage() {
return (
<div className="min-h-screen bg-base-200">
{/* Navbar */}
<div className="navbar bg-base-100 shadow-lg">
<div className="flex-1">
<a className="btn btn-ghost normal-case text-xl">Shop</a>
</div>
<div className="flex-none gap-2">
<div className="form-control">
<input
type="text"
placeholder="Search"
className="input input-bordered w-24 md:w-auto"
/>
</div>
<div className="dropdown dropdown-end">
<label tabIndex={0} className="btn btn-ghost btn-circle">
<div className="indicator">
<svg className="h-5 w-5">...</svg>
<span className="badge badge-sm indicator-item">8</span>
</div>
</label>
</div>
</div>
</div>
{/* Hero */}
<div className="hero min-h-screen bg-base-200">
<div className="hero-content flex-col lg:flex-row-reverse">
<img src="/images/stock/photo-1635805737707-575885ab0820.jpg"
className="max-w-sm rounded-lg shadow-2xl" />
<div>
<h1 className="text-5xl font-bold">Box Office News!</h1>
<p className="py-6">Provident cupiditate voluptatem...</p>
<button className="btn btn-primary">Get Started</button>
</div>
</div>
</div>
{/* Products Grid */}
<div className="container mx-auto px-4 py-8">
<h2 className="text-3xl font-bold mb-6">Featured Products</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{products.map(product => (
<div key={product.id} className="card bg-base-100 shadow-xl">
<figure>
<img src={product.image} alt={product.name} />
</figure>
<div className="card-body">
<h2 className="card-title">
{product.name}
{product.isNew && <div className="badge badge-secondary">NEW</div>}
</h2>
<p>{product.description}</p>
<div className="card-actions justify-end">
<div className="badge badge-outline">${product.price}</div>
<button className="btn btn-primary btn-sm">Buy Now</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)
}混合使用
结合shadcn/ui和DaisyUI
tsx
// 可以在同一个项目中同时使用两者
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
// shadcn/ui主题
colors: {
border: "hsl(var(--border))",
// ...
},
},
},
plugins: [
require("daisyui"), // DaisyUI插件
],
daisyui: {
prefix: "daisy-", // 添加前缀避免冲突
},
}
// 使用示例
function HybridComponent() {
return (
<div>
{/* 使用shadcn/ui的对话框 */}
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
{/* 使用DaisyUI的表单 */}
<div className="daisy-form-control">
<label className="daisy-label">
<span className="daisy-label-text">Email</span>
</label>
<input
type="text"
className="daisy-input daisy-input-bordered"
/>
</div>
</DialogContent>
</Dialog>
</div>
)
}最佳实践
1. 组件抽象
tsx
// 基于shadcn/ui创建应用级组件
import { Button as ShadcnButton } from "@/components/ui/button"
interface AppButtonProps extends React.ComponentProps<typeof ShadcnButton> {
loading?: boolean;
}
export function AppButton({ loading, children, ...props }: AppButtonProps) {
return (
<ShadcnButton disabled={loading} {...props}>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Loading...
</>
) : children}
</ShadcnButton>
)
}
// 基于DaisyUI创建应用级组件
export function AppCard({ children, className = "", ...props }) {
return (
<div className={`card bg-base-100 shadow-xl ${className}`} {...props}>
{children}
</div>
)
}2. 主题一致性
typescript
// 统一主题管理
export const themeConfig = {
shadcn: {
colors: {
primary: "hsl(221.2 83.2% 53.3%)",
secondary: "hsl(210 40% 96.1%)",
},
},
daisy: {
mytheme: {
primary: "#3b82f6",
secondary: "#f0f4f8",
},
},
}
// 确保两个库的主题颜色一致3. 性能优化
typescript
// 按需导入shadcn/ui组件
import { Button } from "@/components/ui/button"
// 而不是 import * as UI from "@/components/ui"
// DaisyUI配置优化
module.exports = {
daisyui: {
themes: ["light", "dark"], // 只使用需要的主题
styled: true,
base: true,
utils: true,
logs: false, // 生产环境关闭日志
},
}总结
Tailwind组件库选择要点:
- shadcn/ui:代码可控、TypeScript友好、高度可定制
- DaisyUI:快速开发、丰富主题、简单易用
- 对比选择:根据项目需求选择合适的库
- 混合使用:可以同时使用两者的优势
- 最佳实践:组件抽象、主题一致、性能优化
选择合适的组件库能显著提升开发效率和代码质量。