Card 卡片
灵活的容器组件,用于将相关信息组织在一个独立的内容块中。Card 是一个复合组件,包含多个子组件用于构建不同区域的布局。
何时使用
- 需要展示一组相关的信息或内容时(如产品卡片、用户信息卡、文章预览)
- 需要在页面中创建视觉上分离的内容块时
- 需要组织复杂的信息层次结构时(标题、描述、内容、操作按钮)
- 需要在网格或列表中展示多个信息项时
快速开始
安装
pnpm
pnpm add @anker-in/headless-ui基础用法
import { Card, CardHeader, CardTitle, CardContent } from '@anker-in/headless-ui'
export default function App() {
return (
<Card>
<CardHeader>
<CardTitle>卡片标题</CardTitle>
</CardHeader>
<CardContent>
卡片内容
</CardContent>
</Card>
)
}演示
加载中...
当前视口: 1920px × 600px场景: 默认状态
打开链接组件结构
Card 是一个复合组件,由以下子组件构成:
<Card>
<CardHeader>
<CardTitle>标题</CardTitle>
<CardDescription>描述</CardDescription>
</CardHeader>
<CardContent>
主要内容
</CardContent>
<CardFooter>
底部操作区
</CardFooter>
</Card>子组件说明
| 组件 | 用途 | 典型内容 |
|---|---|---|
Card | 卡片容器 | 包裹所有子组件 |
CardHeader | 头部区域 | 包含标题和描述 |
CardTitle | 标题 | 卡片的主标题文本 |
CardDescription | 描述 | 卡片的副标题或简短描述 |
CardContent | 内容区域 | 卡片的主要内容 |
CardFooter | 底部区域 | 操作按钮或补充信息 |
布局变体
基础卡片
最简单的卡片布局,仅包含内容和底部操作。
<Card>
<CardContent>
<p>Charge Easily On the Go</p>
<p>Charge Everything Everywhere Faster All at Once</p>
</CardContent>
<CardFooter>
<Button>Learn More</Button>
</CardFooter>
</Card>带头部的卡片
包含标题和描述的完整卡片。
<Card>
<CardHeader>
<CardTitle>快速充电技术</CardTitle>
<CardDescription>为您的设备提供极速充电体验</CardDescription>
</CardHeader>
<CardContent>
<p>采用最新的 PowerIQ 3.0 技术,充电速度提升 30%</p>
</CardContent>
<CardFooter>
<Button>了解更多</Button>
</CardFooter>
</Card>带图片的卡片
图片卡片通常将图片放在 CardContent 的开头。
<Card className="w-[400px]">
<CardContent className="p-0">
<img
src="https://example.com/product.jpg"
alt="Product"
className="w-full rounded-t-lg"
/>
<div className="p-4">
<p>产品描述文本</p>
</div>
</CardContent>
<CardFooter>
<Button>查看详情</Button>
</CardFooter>
</Card>仅内容卡片
有时卡片只需要展示内容,不需要头部和底部。
<Card>
<CardContent>
<h3 className="mb-2 text-lg font-semibold">特性亮点</h3>
<ul className="list-disc space-y-1 pl-5">
<li>高效节能</li>
<li>快速充电</li>
<li>多设备兼容</li>
</ul>
</CardContent>
</Card>响应式布局
Card 组件内置响应式 padding,在不同屏幕尺寸下自动调整内外边距。
默认响应式行为
-
< 1920px: 标准 padding
- Header/Footer:
px-4 pt-6/px-4 pb-6 - Content:
p-4
- Header/Footer:
-
≥ 1920px (lg-desktop): 增大 padding
- Header/Footer:
px-6 pt-8/px-6 pb-8 - Content:
p-6
- Header/Footer:
自定义响应式布局
<Card className="
w-full
tablet:w-[400px]
laptop:w-[450px]
desktop:w-[500px]
">
<CardContent>
响应式宽度卡片
</CardContent>
</Card>网格布局示例
<div className="grid gap-6 tablet:grid-cols-2 laptop:grid-cols-3">
<Card>
<CardHeader>
<CardTitle>卡片 1</CardTitle>
</CardHeader>
<CardContent>内容 1</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>卡片 2</CardTitle>
</CardHeader>
<CardContent>内容 2</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>卡片 3</CardTitle>
</CardHeader>
<CardContent>内容 3</CardContent>
</Card>
</div>Props API
Card Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 卡片内容 |
| className | string | - | ❌ | 自定义 CSS 类名 |
CardHeader Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 头部内容(通常包含 CardTitle 和 CardDescription) |
| className | string | - | ❌ | 自定义 CSS 类名 |
CardTitle Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 标题文本 |
| className | string | - | ❌ | 自定义 CSS 类名 |
CardDescription Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 描述文本 |
| className | string | - | ❌ | 自定义 CSS 类名 |
CardContent Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 主要内容 |
| className | string | - | ❌ | 自定义 CSS 类名 |
CardFooter Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 底部内容(通常包含按钮) |
| className | string | - | ❌ | 自定义 CSS 类名 |
继承的 Props
所有 Card 子组件继承 React.HTMLAttributes<HTMLDivElement> 的所有属性。
类型定义
import * as React from 'react'
// Card 组件
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
// 组件实现
})
Card.displayName = 'Card'
// CardHeader 组件
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
// 组件实现
})
CardHeader.displayName = 'CardHeader'
// CardTitle 组件
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
// 组件实现
})
CardTitle.displayName = 'CardTitle'
// CardDescription 组件
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
// 组件实现
})
CardDescription.displayName = 'CardDescription'
// CardContent 组件
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
// 组件实现
})
CardContent.displayName = 'CardContent'
// CardFooter 组件
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
// 组件实现
})
CardFooter.displayName = 'CardFooter'
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }使用示例
基础产品卡片
展示产品信息的标准卡片。
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from '@anker-in/headless-ui'
export default function ProductCard() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>PowerCore 20K</CardTitle>
<CardDescription>20,000mAh 超大容量移动电源</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm">
<li>✓ 支持 USB-C 双向快充</li>
<li>✓ 可为笔记本电脑充电</li>
<li>✓ 20,000mAh 超大容量</li>
</ul>
</CardContent>
<CardFooter className="justify-between">
<span className="text-2xl font-bold">$49.99</span>
<Button>立即购买</Button>
</CardFooter>
</Card>
)
}文章预览卡片
用于博客或新闻列表的文章卡片。
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from '@anker-in/headless-ui'
export default function ArticleCard() {
return (
<Card>
<CardHeader>
<CardTitle>如何选择合适的充电器</CardTitle>
<CardDescription>发布于 2025-01-15 · 阅读时间 5 分钟</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-600">
选择合适的充电器对于保护设备电池寿命至关重要。
本文将介绍如何根据设备类型、功率需求和使用场景选择最适合的充电方案...
</p>
</CardContent>
<CardFooter>
<Button variant="link">阅读全文 →</Button>
</CardFooter>
</Card>
)
}带图片的产品展示卡片
包含产品图片的展示卡片。
import { Card, CardContent, CardFooter, Button, Picture } from '@anker-in/headless-ui'
export default function ImageProductCard() {
return (
<Card className="w-[400px] overflow-hidden">
<CardContent className="p-0">
<Picture
source="https://images.unsplash.com/photo-1609591328607-e0c4e2f1c4b3"
alt="Wireless Charger"
className="h-[250px] w-full object-cover"
/>
<div className="p-4">
<h3 className="mb-2 text-xl font-semibold">无线充电板</h3>
<p className="text-sm text-gray-600">
支持 iPhone 和 AirPods 同时充电,15W 快充,安全高效
</p>
</div>
</CardContent>
<CardFooter className="justify-between pt-0">
<span className="text-xl font-bold">$39.99</span>
<Button variant="secondary">加入购物车</Button>
</CardFooter>
</Card>
)
}用户信息卡片
展示用户资料的卡片。
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from '@anker-in/headless-ui'
export default function UserProfileCard() {
return (
<Card className="w-[350px]">
<CardHeader>
<div className="mb-4 flex items-center gap-4">
<img
src="https://i.pravatar.cc/100"
alt="User avatar"
className="size-16 rounded-full"
/>
<div>
<CardTitle>张明</CardTitle>
<CardDescription>产品设计师</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
<p className="text-sm">
热爱用户体验设计,专注于创造简洁优雅的产品界面。
5 年设计经验,服务过 20+ 客户。
</p>
</CardContent>
<CardFooter className="gap-2">
<Button variant="secondary" size="sm">发送消息</Button>
<Button variant="link" size="sm">查看主页</Button>
</CardFooter>
</Card>
)
}统计数据卡片
展示关键指标的卡片。
import { Card, CardHeader, CardTitle, CardContent } from '@anker-in/headless-ui'
export default function StatsCard() {
return (
<Card>
<CardHeader>
<CardDescription>总销售额</CardDescription>
<CardTitle className="text-3xl">$45,231.89</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-green-600">
↑ 20.1% 较上月
</p>
</CardContent>
</Card>
)
}表单卡片
包含表单输入的卡片。
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button, Input } from '@anker-in/headless-ui'
export default function FormCard() {
return (
<Card className="w-[450px]">
<CardHeader>
<CardTitle>创建账户</CardTitle>
<CardDescription>填写信息以创建新账户</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium">姓名</label>
<Input placeholder="请输入姓名" />
</div>
<div>
<label className="mb-1 block text-sm font-medium">邮箱</label>
<Input type="email" placeholder="your@email.com" />
</div>
<div>
<label className="mb-1 block text-sm font-medium">密码</label>
<Input type="password" placeholder="••••••••" />
</div>
</CardContent>
<CardFooter className="justify-between">
<Button variant="secondary">取消</Button>
<Button>创建账户</Button>
</CardFooter>
</Card>
)
}可访问性
语义化 HTML
Card 组件使用 <div> 元素,可根据内容语义添加适当的 ARIA 属性。
{/* 用作文章卡片 */}
<Card as="article">
<CardHeader>
<CardTitle>文章标题</CardTitle>
</CardHeader>
<CardContent>内容</CardContent>
</Card>
{/* 用作导航卡片 */}
<Card as="nav" aria-label="快速导航">
<CardContent>导航链接</CardContent>
</Card>ARIA 标签
为卡片提供描述性的 ARIA 标签以增强可访问性。
{/* 可点击的卡片 */}
<Card
role="button"
tabIndex={0}
aria-label="查看产品详情"
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
>
<CardContent>产品内容</CardContent>
</Card>
{/* 带状态的卡片 */}
<Card aria-live="polite" aria-busy={isLoading}>
<CardContent>
{isLoading ? '加载中...' : '内容'}
</CardContent>
</Card>键盘导航
确保卡片中的交互元素可通过键盘访问。
<Card>
<CardContent>
<button
className="w-full text-left"
onFocus={() => console.log('focused')}
>
可聚焦内容
</button>
</CardContent>
<CardFooter>
<Button>操作按钮</Button>
</CardFooter>
</Card>最佳实践
为交互式卡片添加焦点样式
如果卡片本身可点击,确保有清晰的焦点指示器。
<Card
tabIndex={0}
className="cursor-pointer transition-all hover:shadow-lg focus:ring-2 focus:ring-primary"
onClick={handleClick}
>
<CardContent>可点击的卡片</CardContent>
</Card>使用正确的标题层级
根据页面结构使用合适的标题标签。
<Card>
<CardHeader>
{/* 如果这是页面的主标题,使用 h1 */}
<CardTitle as="h2">卡片标题</CardTitle>
</CardHeader>
</Card>为图片提供替代文本
确保所有图片都有描述性的 alt 文本。
<Card>
<CardContent className="p-0">
<img
src="/product.jpg"
alt="Anker PowerCore 20K 移动电源,黑色外观,侧面有 USB-C 接口"
/>
</CardContent>
</Card>确保足够的颜色对比度
卡片内的文本和背景应有足够的对比度(至少 4.5:1)。
测试屏幕阅读器
使用 VoiceOver、NVDA 或 JAWS 测试卡片内容的可读性。
最佳实践
✅ 推荐做法
- 保持卡片内容简洁: 避免在单个卡片中放入过多信息
{/* ✅ 推荐:简洁的信息层次 */}
<Card>
<CardHeader>
<CardTitle>快速充电</CardTitle>
<CardDescription>30 分钟充满 50%</CardDescription>
</CardHeader>
<CardContent>
<p>采用最新 PowerIQ 3.0 技术</p>
</CardContent>
<CardFooter>
<Button>了解更多</Button>
</CardFooter>
</Card>- 统一卡片尺寸: 在网格布局中使用一致的卡片尺寸
<div className="grid gap-4 tablet:grid-cols-3">
<Card className="h-[300px]">内容 1</Card>
<Card className="h-[300px]">内容 2</Card>
<Card className="h-[300px]">内容 3</Card>
</div>- 合理使用 CardFooter: 将操作按钮放在底部,保持对齐
<CardFooter className="justify-between">
<span>价格: $49.99</span>
<Button>购买</Button>
</CardFooter>- 响应式图片: 使用响应式图片避免布局撕裂
<Card>
<CardContent className="p-0">
<img
src="/image.jpg"
alt="Product"
className="aspect-video w-full object-cover"
/>
</CardContent>
</Card>❌ 避免做法
- 不要嵌套卡片: 避免在 Card 内部嵌套另一个 Card
{/* ❌ 错误:嵌套卡片 */}
<Card>
<CardContent>
<Card>
<CardContent>嵌套内容</CardContent>
</Card>
</CardContent>
</Card>
{/* ✅ 正确:使用其他容器 */}
<Card>
<CardContent>
<div className="rounded-lg border p-4">
分组内容
</div>
</CardContent>
</Card>- 不要覆盖默认 padding: 除非必要,保留子组件的默认 padding
{/* ❌ 错误:破坏默认间距 */}
<Card>
<CardHeader className="p-0">
<CardTitle>标题</CardTitle>
</CardHeader>
</Card>
{/* ✅ 正确:使用默认间距 */}
<Card>
<CardHeader>
<CardTitle>标题</CardTitle>
</CardHeader>
</Card>- 不要在 CardHeader 中放置过多内容: CardHeader 应简洁明了
{/* ❌ 错误:Header 过于复杂 */}
<CardHeader>
<CardTitle>标题</CardTitle>
<CardDescription>描述</CardDescription>
<div>大量其他内容...</div>
<Button>按钮</Button>
</CardHeader>
{/* ✅ 正确:内容放在 CardContent */}
<CardHeader>
<CardTitle>标题</CardTitle>
<CardDescription>描述</CardDescription>
</CardHeader>
<CardContent>
详细内容和其他元素
</CardContent>常见问题
如何自定义卡片的圆角?
使用 className 覆盖默认的 rounded-lg:
<Card className="rounded-2xl">
<CardContent>更大的圆角</CardContent>
</Card>如何创建可点击的卡片?
添加点击事件和相应的样式:
<Card
className="cursor-pointer transition-all hover:shadow-lg"
onClick={() => console.log('clicked')}
tabIndex={0}
role="button"
>
<CardContent>点击我</CardContent>
</Card>如何移除卡片的阴影和边框?
覆盖默认样式:
<Card className="border-0 shadow-none">
<CardContent>无边框无阴影</CardContent>
</Card>如何在卡片中使用自定义 padding?
通过 className 自定义各个子组件的 padding:
<Card>
<CardHeader className="px-8 pt-8">
<CardTitle>自定义 padding</CardTitle>
</CardHeader>
<CardContent className="px-8">
内容
</CardContent>
<CardFooter className="px-8 pb-8">
底部
</CardFooter>
</Card>如何实现卡片的悬停效果?
使用 Tailwind CSS 的 hover: 前缀:
<Card className="transition-all duration-300 hover:scale-105 hover:shadow-xl">
<CardContent>悬停时放大</CardContent>
</Card>如何创建水平布局的卡片?
调整布局方向和子组件排列:
<Card className="flex flex-row">
<CardContent className="flex-shrink-0 p-0">
<img src="/image.jpg" alt="Product" className="h-full w-[200px] object-cover" />
</CardContent>
<div className="flex flex-1 flex-col">
<CardHeader>
<CardTitle>水平卡片</CardTitle>
<CardDescription>左图右文布局</CardDescription>
</CardHeader>
<CardContent className="flex-1">
详细描述内容
</CardContent>
<CardFooter>
<Button>操作</Button>
</CardFooter>
</div>
</Card>如何在移动端优化卡片布局?
使用响应式类名:
<Card className="
w-full
tablet:w-[350px]
laptop:w-[400px]
">
<CardContent className="
p-3
tablet:p-4
laptop:p-6
">
响应式内容
</CardContent>
</Card>相关资源
- Storybook 文档 - 查看所有变体和交互示例
- GitHub 源代码 - 查看完整实现代码
- Button 组件 - 常用于 CardFooter 中的操作按钮
- 无障碍性指南 (WCAG) - 了解更多无障碍标准
更新日志
v2.0.0 (2025-01-17)
- 🎉 完善组件文档,添加详细的使用指南和最佳实践
- ✨ 响应式 padding 支持(lg-desktop 断点)
- 🔧 改进子组件的间距和排版
- 📝 添加丰富的使用示例(产品卡片、文章卡片、表单卡片等)
本文档有帮助吗?
在 GitHub 上反馈
Last updated on