字节笔记本
2026年7月20日
Next.js + Tailwind CSS + shadcn/ui:搭建现代前端项目与实现深色模式
Next.js + Tailwind CSS + shadcn/ui:搭建现代前端项目与实现深色模式
Next.js、Tailwind CSS 和 shadcn/ui 的组合是目前 React 生态中比较主流的技术选型。本文从项目初始化开始,一步步搭建一个支持深色模式切换的现代前端项目。
项目初始化
创建 Next.js 项目
npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app创建时选择 TypeScript、Tailwind CSS 和 ESLint,项目会自动完成基础配置。
安装 shadcn/ui
npx shadcn@latest init初始化时会提示几个配置选项:
- 样式风格:New York
- 基础颜色:Zinc
- 是否使用 CSS 变量:yes
配置完成后,components.json 文件会生成在项目根目录,后续添加组件时会用到。
# 添加需要的组件
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dropdown-menu推荐的项目结构
my-app/
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ui/ # shadcn/ui 组件(自动生成)
│ └── custom/ # 业务组件
├── lib/
│ └── utils.ts # cn() 等工具函数
└── types/实现深色模式
深色模式是现代应用的标配功能。使用 next-themes 库可以比较方便地实现。
安装依赖
npm install next-themes配置 Tailwind CSS
// tailwind.config.ts
const config = {
darkMode: 'class',
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
}关键配置是 darkMode: 'class',表示通过在 <html> 标签上切换 dark class 来控制深色模式。
创建 ThemeProvider
// app/providers.tsx
'use client'
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 '@/app/providers'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}几个配置项说明:
attribute="class":使用 class 方式切换主题defaultTheme="system":默认跟随系统设置enableSystem:启用系统主题检测disableTransitionOnChange:切换时禁用过渡动画,避免闪烁suppressHydrationWarning:防止服务端渲染和客户端不一致的警告
创建主题切换组件
// components/theme-toggle.tsx
'use client'
import { Moon, Sun } from 'lucide-react'
import { useTheme } from 'next-themes'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
export function ThemeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">切换主题</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
浅色模式
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
深色模式
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
跟随系统
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}太阳和月亮图标的切换用了 CSS transform 动画,通过 dark: 前缀控制显示隐藏。
在页面中使用
// components/nav-bar.tsx
import { ThemeToggle } from '@/components/theme-toggle'
import { Button } from '@/components/ui/button'
export function NavBar() {
return (
<nav className="flex items-center justify-between p-4 bg-white dark:bg-gray-900">
<div className="text-xl font-bold dark:text-white">我的应用</div>
<div className="flex gap-4 items-center">
<ThemeToggle />
<Button variant="outline">登录</Button>
</div>
</nav>
)
}样式适配要点
组件级别的深色适配
用 dark: 前缀为每个需要变化的元素指定深色样式:
<div className="bg-white dark:bg-gray-900 min-h-screen">
<h1 className="text-gray-900 dark:text-white">标题</h1>
<p className="text-gray-600 dark:text-gray-300">正文内容</p>
</div>CSS 变量方式(shadcn/ui 推荐)
shadcn/ui 使用 CSS 变量管理主题色,在 globals.css 中定义:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
/* ...更多变量 */
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
/* ...更多变量 */
}
}用 CSS 变量的好处是修改一处,所有引用该变量的组件都会跟着变化,不需要逐个修改。
防止页面闪烁
页面首次加载时,如果深色模式还没设置好就渲染了浅色页面,会看到一次闪烁。可以在 <head> 中加一段内联脚本提前设置:
// app/layout.tsx
<html lang="zh-CN" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{
__html: `
try {
const theme = localStorage.getItem('theme')
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
}
} catch (_) {}
`
}} />
</head>
<body>...</body>
</html>这段脚本在页面渲染前执行,确保 <html> 标签在首次绘制时就有正确的 class。
实战示例:响应式仪表盘
// app/dashboard/page.tsx
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
export default function DashboardPage() {
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-6 text-gray-900 dark:text-white">
仪表盘
</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle>总用户</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">1,234</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>日活跃</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">567</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>转化率</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">12.3%</p>
</CardContent>
</Card>
</div>
</div>
)
}shadcn/ui 的 Card 组件已经内置了对 CSS 变量的支持,所以深色模式下无需额外适配。
Tailwind CSS 响应式设计
Tailwind 的响应式断点是移动优先的,从小到大依次为:
| 前缀 | 最小宽度 | 适用设备 |
|---|---|---|
| (无) | 0px | 手机 |
sm | 640px | 大屏手机 |
md | 768px | 平板 |
lg | 1024px | 笔记本 |
xl | 1280px | 桌面 |
2xl | 1536px | 大屏桌面 |
// 同一个元素在不同断点显示不同列数
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{items.map(item => <Card key={item.id} />)}
</div>总结
这套技术栈的分工很清晰:Next.js 负责路由和渲染,Tailwind CSS 处理样式,shadcn/ui 提供基础组件。深色模式通过 next-themes + Tailwind 的 dark: 前缀实现,shadcn/ui 的 CSS 变量机制让主题切换更加统一。实际开发中,建议优先使用 shadcn/ui 提供的组件,减少重复造轮子,同时利用 Tailwind 的工具类做布局和细节调整。