07. 样式方案实战
Next.js 支持多种样式方案,包括 CSS Modules、Tailwind CSS、Styled Components 等。本章将重点讲解最常用的 Tailwind CSS 和 CSS Modules。
1. Tailwind CSS(推荐)
Tailwind CSS 是一个实用优先的 CSS 框架,通过类名快速构建界面。
为什么选择 Tailwind?
- ✅ 快速开发:不需要写自定义 CSS
- ✅ 响应式设计:内置断点系统
- ✅ 按需打包:只打包使用的样式
- ✅ 一致性:设计系统统一
基础用法
// src/app/page.tsx
export default function Home() {
return (
<div className="min-h-screen bg-gray-100">
<div className="max-w-4xl mx-auto px-4 py-20">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
欢迎使用 Tailwind CSS
</h1>
<p className="text-lg text-gray-600 mb-8">
这是一个使用 Tailwind CSS 构建的页面
</p>
<button className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors">
点击我
</button>
</div>
</div>
)
}常用工具类
布局
<div className="flex items-center justify-between">
<div>左侧</div>
<div>右侧</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>项目 1</div>
<div>项目 2</div>
<div>项目 3</div>
</div>间距
<div className="p-4 m-2">内边距 4,外边距 2</div>
<div className="px-6 py-4">水平 6,垂直 4</div>
<div className="mt-8 mb-4">上边距 8,下边距 4</div>颜色
<div className="bg-blue-500 text-white">蓝色背景,白色文字</div>
<div className="text-gray-600 border-gray-300">灰色文字和边框</div>响应式设计
<div className="text-sm md:text-base lg:text-lg xl:text-xl">
响应式文字大小
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
响应式网格
</div>2. 实战案例:卡片组件
基础卡片
// src/components/Card.tsx
interface CardProps {
title: string
description: string
image?: string
}
export default function Card({ title, description, image }: CardProps) {
return (
<div className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow">
{image && (
<img
src={image}
alt={title}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<h3 className="text-xl font-semibold mb-2">{title}</h3>
<p className="text-gray-600">{description}</p>
</div>
</div>
)
}使用示例
// src/app/products/page.tsx
import Card from '@/components/Card'
export default function ProductsPage() {
return (
<div className="max-w-6xl mx-auto px-4 py-20">
<h1 className="text-4xl font-bold mb-8">商品列表</h1>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<Card
title="商品 A"
description="这是商品 A 的描述"
image="/images/product-a.jpg"
/>
<Card
title="商品 B"
description="这是商品 B 的描述"
image="/images/product-b.jpg"
/>
<Card
title="商品 C"
description="这是商品 C 的描述"
image="/images/product-c.jpg"
/>
</div>
</div>
)
}3. CSS Modules
CSS Modules 允许你编写局部作用域的 CSS,避免样式冲突。
创建 CSS Module
/* src/components/Button.module.css */
.button {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s;
}
.primary {
background-color: #2563eb;
color: white;
}
.primary:hover {
background-color: #1d4ed8;
}
.secondary {
background-color: #e5e7eb;
color: #374151;
}
.secondary:hover {
background-color: #d1d5db;
}在组件中使用
// src/components/Button.tsx
import styles from './Button.module.css'
interface ButtonProps {
children: React.ReactNode
variant?: 'primary' | 'secondary'
onClick?: () => void
}
export default function Button({
children,
variant = 'primary',
onClick,
}: ButtonProps) {
return (
<button
className={`${styles.button} ${styles[variant]}`}
onClick={onClick}
>
{children}
</button>
)
}组合类名
使用 clsx 或 classnames 库组合类名:
npm install clsx// src/components/Button.tsx
import clsx from 'clsx'
import styles from './Button.module.css'
export default function Button({
children,
variant = 'primary',
disabled = false,
onClick,
}: ButtonProps) {
return (
<button
className={clsx(styles.button, styles[variant], {
[styles.disabled]: disabled,
})}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
)
}4. 全局样式
使用 globals.css
/* src/app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 自定义全局样式 */
:root {
--primary-color: #2563eb;
--secondary-color: #64748b;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 自定义工具类 */
@layer components {
.btn-primary {
@apply bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700;
}
.card {
@apply bg-white rounded-lg shadow-md p-6;
}
}在组件中使用自定义工具类
// src/app/page.tsx
export default function Home() {
return (
<div>
<button className="btn-primary">主要按钮</button>
<div className="card">卡片内容</div>
</div>
)
}5. 实战案例:响应式导航栏
使用 Tailwind CSS 实现
// src/components/Navbar.tsx
'use client'
import { useState } from 'react'
import Link from 'next/link'
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false)
return (
<nav className="bg-white shadow-md">
<div className="max-w-6xl mx-auto px-4">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<Link href="/" className="text-2xl font-bold text-blue-600">
我的网站
</Link>
{/* 桌面导航 */}
<div className="hidden md:flex gap-6">
<Link href="/" className="text-gray-700 hover:text-blue-600">
首页
</Link>
<Link href="/about" className="text-gray-700 hover:text-blue-600">
关于
</Link>
<Link href="/blog" className="text-gray-700 hover:text-blue-600">
博客
</Link>
<Link href="/contact" className="text-gray-700 hover:text-blue-600">
联系
</Link>
</div>
{/* 移动端菜单按钮 */}
<button
className="md:hidden text-gray-700"
onClick={() => setIsOpen(!isOpen)}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
</div>
{/* 移动端菜单 */}
{isOpen && (
<div className="md:hidden py-4 space-y-2">
<Link
href="/"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setIsOpen(false)}
>
首页
</Link>
<Link
href="/about"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setIsOpen(false)}
>
关于
</Link>
<Link
href="/blog"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setIsOpen(false)}
>
博客
</Link>
<Link
href="/contact"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setIsOpen(false)}
>
联系
</Link>
</div>
)}
</div>
</nav>
)
}6. 实战案例:表单样式
使用 Tailwind CSS
// src/components/ContactForm.tsx
'use client'
import { useState } from 'react'
export default function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// 处理提交
console.log(formData)
}
return (
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto space-y-6">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700 mb-2"
>
姓名
</label>
<input
type="text"
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-2"
>
邮箱
</label>
<input
type="email"
id="email"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label
htmlFor="message"
className="block text-sm font-medium text-gray-700 mb-2"
>
留言
</label>
<textarea
id="message"
value={formData.message}
onChange={(e) =>
setFormData({ ...formData, message: e.target.value })
}
rows={6}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition-colors font-medium"
>
发送消息
</button>
</form>
)
}7. 自定义 Tailwind 配置
修改 tailwind.config.js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
// ... 更多颜色
900: '#1e3a8a',
},
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
plugins: [],
}使用自定义颜色
<div className="bg-primary-500 text-primary-50">
使用自定义颜色
</div>8. 样式方案选择指南
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 快速原型开发 | Tailwind CSS | 开发速度快 |
| 组件库开发 | CSS Modules | 样式隔离,易于维护 |
| 大型项目 | Tailwind + CSS Modules | 结合两者优势 |
| 需要主题切换 | CSS Variables + Tailwind | 动态主题支持 |
9. 实战练习:构建响应式卡片网格
需求
创建一个响应式卡片网格,要求:
- 移动端:1 列
- 平板:2 列
- 桌面:3 列
- 卡片有悬停效果
实现
// src/app/gallery/page.tsx
const items = [
{ id: 1, title: '项目 1', description: '描述 1' },
{ id: 2, title: '项目 2', description: '描述 2' },
{ id: 3, title: '项目 3', description: '描述 3' },
{ id: 4, title: '项目 4', description: '描述 4' },
{ id: 5, title: '项目 5', description: '描述 5' },
{ id: 6, title: '项目 6', description: '描述 6' },
]
export default function GalleryPage() {
return (
<div className="min-h-screen bg-gray-100 py-20">
<div className="max-w-7xl mx-auto px-4">
<h1 className="text-4xl font-bold mb-12 text-center">作品展示</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((item) => (
<div
key={item.id}
className="bg-white rounded-lg shadow-md p-6 hover:shadow-xl transition-shadow transform hover:-translate-y-1"
>
<h3 className="text-xl font-semibold mb-2">{item.title}</h3>
<p className="text-gray-600">{item.description}</p>
</div>
))}
</div>
</div>
</div>
)
}10. 总结
本章我们学习了:
- ✅ Tailwind CSS 的基础用法和常用工具类
- ✅ CSS Modules 的使用方法
- ✅ 全局样式的配置
- ✅ 响应式设计的实现
- ✅ 构建了导航栏、表单等实战案例
关键要点:
- 🎨 Tailwind CSS 适合快速开发
- 🎨 CSS Modules 适合组件样式隔离
- 🎨 响应式设计使用 Tailwind 的断点系统
- 🎨 可以结合使用多种样式方案
下一步:在下一章,我们将学习静态资源管理,包括图片、字体等的使用。