expandable-card
Expandable Card Pattern
Build smooth expand/collapse animations using CSS grid-rows, avoiding height:auto animation issues.
Why grid-rows?
Traditional height animation requires explicit pixel values. The grid-rows technique allows smooth animation to/from auto height:
grid-rows-[0fr]+overflow-hidden= collapsed (0 height)grid-rows-[1fr]= expanded (natural height)
Core Implementation
"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>
);
}
Key Elements
1. State Management
const [expanded, setExpanded] = useState(true); // Start expanded
// or
const [expanded, setExpanded] = useState(false); // Start collapsed
2. Header Click Handler
<div
className="cursor-pointer hover:bg-zinc-50 transition-colors"
onClick={() => setExpanded(!expanded)}
>
3. ChevronDown Rotation
<ChevronDown
className={`transition-transform duration-200 ${
expanded ? "rotate-180" : ""
}`}
/>
4. Grid Container Animation
<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>
Timing Recommendations
| Duration | Use Case |
|---|---|
duration-150 |
Small cards, quick feedback |
duration-200 |
Chevron rotation |
duration-300 |
Content expansion (recommended) |
duration-500 |
Large content areas |
Shadow Transition (Optional)
Add shadow that changes with state:
<div
className={`rounded-xl border overflow-hidden transition-all duration-300 ${
expanded ? "shadow-lg" : "shadow-sm hover:shadow-md"
}`}
>
Accessibility Considerations
<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)
const [expandedId, setExpandedId] = useState<string | null>("first");
// Toggle logic
onClick={() => setExpandedId(expandedId === id ? null : id)}
Nested Content Protection
Prevent clicks on interactive content from toggling:
<a
href="..."
onClick={(e) => e.stopPropagation()}
>
Important: Always add stopPropagation to:
- Links (
<a>) - Buttons that perform actions other than toggling
- Form inputs
- Any interactive element that shouldn't trigger expand/collapse
// 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>
Checklist
-
overflow-hiddenon inner wrapper (required for animation) -
transition-allon grid container - ChevronDown has
transition-transform - Header has
cursor-pointerand hover state - Timing consistent (300ms recommended)
More from ainergiz/design-inspirations
glassmorphism
Creates frosted glass UI elements with blur, transparency, and subtle borders. Use when building overlays, floating controls, tooltips, or any element that should appear elevated with a translucent background.
169image-carousel
Creates image carousels with hover-activated auto-advance, touch swipe support, and animated progress indicators. Use when building image galleries, product showcases, or any multi-image display with navigation.
6stacked-cards
Creates horizontally fanned/cascading card stacks with proper z-index ordering and hover lift animations. Use when building album browsers, card fans, stacked previews, or any overlapping card collection.
5nested-card
Creates cards with an outer gradient container and inner content card. Use when building premium card designs with depth, layered card layouts, or cards with image sections and content sections.
5create-new-design
Scaffolds a new design in the design-inspirations repo with preview component, detail page, and main page entry. Use when creating a new design, adding a design inspiration, or scaffolding design files.
5dropdown-menu
Creates dropdown menus with proper click-outside detection and z-index stacking for list contexts. Use when building action menus, context menus, or any dropdown that appears in cards/list items.
4