expandable-card
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExpandable Card Pattern
可展开卡片模式
Build smooth expand/collapse animations using CSS grid-rows, avoiding height:auto animation issues.
使用CSS grid-rows构建流畅的展开/折叠动画,避免height:auto的动画问题。
Why grid-rows?
为什么选择grid-rows?
Traditional height animation requires explicit pixel values. The technique allows smooth animation to/from height:
grid-rowsauto- +
grid-rows-[0fr]= collapsed (0 height)overflow-hidden - = expanded (natural height)
grid-rows-[1fr]
传统的高度动画需要明确的像素值。技术支持平滑地在高度之间切换动画:
grid-rowsauto- +
grid-rows-[0fr]= 折叠状态(高度为0)overflow-hidden - = 展开状态(自然高度)
grid-rows-[1fr]
Core Implementation
核心实现
tsx
"use client";
import { useState } from "react";
import { ChevronDown } from "lucide-react";
function ExpandableCard() {
const [expanded, setExpanded] = useState(true);
return (
<div className="rounded-xl border overflow-hidden transition-all duration-300">
{/* Header - clickable toggle */}
<div
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-zinc-50 transition-colors"
onClick={() => setExpanded(!expanded)}
>
<span className="font-medium">Card Title</span>
<ChevronDown
className={`w-5 h-5 transition-transform duration-200 ${
expanded ? "rotate-180" : ""
}`}
/>
</div>
{/* Content - animated container */}
<div
className={`grid transition-all duration-300 ease-in-out ${
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
}`}
>
<div className="overflow-hidden">
<div className="px-4 py-4 border-t">
{/* Your content here */}
<p>Expandable content goes here.</p>
</div>
</div>
</div>
</div>
);
}tsx
"use client";
import { useState } from "react";
import { ChevronDown } from "lucide-react";
function ExpandableCard() {
const [expanded, setExpanded] = useState(true);
return (
<div className="rounded-xl border overflow-hidden transition-all duration-300">
{/* 头部 - 可点击的切换区域 */}
<div
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-zinc-50 transition-colors"
onClick={() => setExpanded(!expanded)}
>
<span className="font-medium">卡片标题</span>
<ChevronDown
className={`w-5 h-5 transition-transform duration-200 ${
expanded ? "rotate-180" : ""
}`}
/>
</div>
{/* 内容 - 动画容器 */}
<div
className={`grid transition-all duration-300 ease-in-out ${
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
}`}
>
<div className="overflow-hidden">
<div className="px-4 py-4 border-t">
{/* 在此处添加你的内容 */}
<p>可展开内容放置于此。</p>
</div>
</div>
</div>
</div>
);
}Key Elements
关键元素
1. State Management
1. 状态管理
tsx
const [expanded, setExpanded] = useState(true); // Start expanded
// or
const [expanded, setExpanded] = useState(false); // Start collapsedtsx
const [expanded, setExpanded] = useState(true); // 默认展开
// 或者
const [expanded, setExpanded] = useState(false); // 默认折叠2. Header Click Handler
2. 头部点击事件处理
tsx
<div
className="cursor-pointer hover:bg-zinc-50 transition-colors"
onClick={() => setExpanded(!expanded)}
>tsx
<div
className="cursor-pointer hover:bg-zinc-50 transition-colors"
onClick={() => setExpanded(!expanded)}
>3. ChevronDown Rotation
3. ChevronDown 旋转效果
tsx
<ChevronDown
className={`transition-transform duration-200 ${
expanded ? "rotate-180" : ""
}`}
/>tsx
<ChevronDown
className={`transition-transform duration-200 ${
expanded ? "rotate-180" : ""
}`}
/>4. Grid Container Animation
4. Grid容器动画
tsx
<div
className={`grid transition-all duration-300 ease-in-out ${
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
}`}
>
<div className="overflow-hidden">
{/* Content wrapper - REQUIRED for animation */}
</div>
</div>tsx
<div
className={`grid transition-all duration-300 ease-in-out ${
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
}`}
>
<div className="overflow-hidden">
{/* 内容包裹层 - 动画必需 */}
</div>
</div>Timing Recommendations
时长建议
| Duration | Use Case |
|---|---|
| Small cards, quick feedback |
| Chevron rotation |
| Content expansion (recommended) |
| Large content areas |
| 时长值 | 使用场景 |
|---|---|
| 小型卡片,快速反馈 |
| Chevron图标旋转 |
| 内容展开(推荐值) |
| 大型内容区域 |
Shadow Transition (Optional)
阴影过渡(可选)
Add shadow that changes with state:
tsx
<div
className={`rounded-xl border overflow-hidden transition-all duration-300 ${
expanded ? "shadow-lg" : "shadow-sm hover:shadow-md"
}`}
>添加随状态变化的阴影效果:
tsx
<div
className={`rounded-xl border overflow-hidden transition-all duration-300 ${
expanded ? "shadow-lg" : "shadow-sm hover:shadow-md"
}`}
>Accessibility Considerations
无障碍适配考虑
tsx
<div
role="button"
tabIndex={0}
aria-expanded={expanded}
onClick={() => setExpanded(!expanded)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setExpanded(!expanded);
}
}}
>tsx
<div
role="button"
tabIndex={0}
aria-expanded={expanded}
onClick={() => setExpanded(!expanded)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setExpanded(!expanded);
}
}}
>Common Variations
常见变体
Multiple Cards (Accordion)
多卡片模式(手风琴)
tsx
const [expandedId, setExpandedId] = useState<string | null>("first");
// Toggle logic
onClick={() => setExpandedId(expandedId === id ? null : id)}tsx
const [expandedId, setExpandedId] = useState<string | null>("first");
// 切换逻辑
onClick={() => setExpandedId(expandedId === id ? null : id)}Nested Content Protection
嵌套内容防护
Prevent clicks on interactive content from toggling:
tsx
<a
href="..."
onClick={(e) => e.stopPropagation()}
>Important: Always add to:
stopPropagation- Links ()
<a> - Buttons that perform actions other than toggling
- Form inputs
- Any interactive element that shouldn't trigger expand/collapse
tsx
// Full example with multiple interactive elements
<div className="overflow-hidden">
<div className="px-4 py-4 border-t">
<a
href="https://example.com"
onClick={(e) => e.stopPropagation()}
className="text-blue-500 hover:underline"
>
External link
</a>
<button
onClick={(e) => {
e.stopPropagation();
// Handle button action
}}
>
Action Button
</button>
</div>
</div>防止点击交互内容时触发折叠/展开:
tsx
<a
href="..."
onClick={(e) => e.stopPropagation()}
>重要提示:务必为以下元素添加:
stopPropagation- 链接 ()
<a> - 执行切换以外操作的按钮
- 表单输入框
- 任何不应触发展开/折叠的交互元素
tsx
// 包含多个交互元素的完整示例
<div className="overflow-hidden">
<div className="px-4 py-4 border-t">
<a
href="https://example.com"
onClick={(e) => e.stopPropagation()}
className="text-blue-500 hover:underline"
>
外部链接
</a>
<button
onClick={(e) => {
e.stopPropagation();
// 处理按钮操作
}}
>
操作按钮
</button>
</div>
</div>Checklist
检查清单
- on inner wrapper (required for animation)
overflow-hidden - on grid container
transition-all - ChevronDown has
transition-transform - Header has and hover state
cursor-pointer - Timing consistent (300ms recommended)
- 内部包裹层添加(动画必需)
overflow-hidden - Grid容器添加
transition-all - ChevronDown添加
transition-transform - 头部添加和悬停状态
cursor-pointer - 时长保持一致(推荐300ms)