Overview
The CardContainer component is a lightweight wrapper that provides a consistent card-style container for grouping related content. It features optional click handling, making it perfect for both static content displays and interactive elements.
Source : src/shared/ui/CardContainer/CardContainer.tsx
Basic Usage
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
function Example () {
return (
< CardContainer >
< h2 > Card Title </ h2 >
< p > Card content goes here </ p >
</ CardContainer >
);
}
Props
The content to display inside the card container. Can include any React elements: text, images, buttons, forms, or other components.
Optional click handler function. When provided, the entire card becomes clickable. Useful for navigation, selection, or triggering actions.
Static Card
Basic card without click interaction:
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
< CardContainer >
< Typography as = "h3" weight = "medium" >
Static Card
</ Typography >
< Typography as = "p" size = "text-sm" >
This card displays information without any interaction.
</ Typography >
</ CardContainer >
Clickable Card
Card with click handler for interactive use cases:
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
function InteractiveCard () {
const handleClick = () => {
console . log ( 'Card clicked!' );
// Navigate, open modal, etc.
};
return (
< CardContainer onClick = { handleClick } >
< h3 > Click me! </ h3 >
< p > This entire card is clickable. </ p >
</ CardContainer >
);
}
Real-World Examples
Auction Item Card
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
import { Button } from '@/shared/ui/button/Button' ;
function AuctionItemCard ({ item }) {
return (
< CardContainer >
< img
src = { item . imageUrl }
alt = { item . title }
style = { { width: '100%' , borderRadius: '8px' , marginBottom: '12px' } }
/>
< Typography as = "h3" weight = "medium" >
{ item . title }
</ Typography >
< Typography as = "p" size = "text-sm" style = { { color: '#666' , margin: '8px 0' } } >
{ item . description }
</ Typography >
< div style = { { display: 'flex' , justifyContent: 'space-between' , alignItems: 'center' } } >
< div >
< Typography as = "p" size = "text-xs" weight = "light" >
Current Bid
</ Typography >
< Typography as = "p" weight = "bold" style = { { fontSize: '20px' , color: '#2563eb' } } >
$ { item . currentBid }
</ Typography >
</ div >
< Button variant = "primary" size = "sm" >
Place Bid
</ Button >
</ div >
</ CardContainer >
);
}
Clickable Category Card
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
import { useRouter } from 'next/router' ;
import { Package } from 'lucide-react' ;
function CategoryCard ({ category }) {
const router = useRouter ();
const handleClick = () => {
router . push ( `/auctions/category/ ${ category . slug } ` );
};
return (
< CardContainer onClick = { handleClick } >
< div style = { {
display: 'flex' ,
alignItems: 'center' ,
gap: '12px' ,
cursor: 'pointer'
} } >
< Package size = { 32 } color = "#2563eb" />
< div >
< Typography as = "h4" weight = "medium" >
{ category . name }
</ Typography >
< Typography as = "p" size = "text-xs" style = { { color: '#666' } } >
{ category . itemCount } active auctions
</ Typography >
</ div >
</ div >
</ CardContainer >
);
}
Dashboard Stats Card
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
import { TrendingUp , TrendingDown } from 'lucide-react' ;
function StatsCard ({ title , value , change , isPositive }) {
return (
< CardContainer >
< Typography as = "p" size = "text-sm" weight = "light" style = { { color: '#666' } } >
{ title }
</ Typography >
< div style = { { display: 'flex' , alignItems: 'baseline' , gap: '8px' , margin: '8px 0' } } >
< Typography as = "p" style = { { fontSize: '32px' , fontWeight: 'bold' } } >
{ value }
</ Typography >
< div style = { {
display: 'flex' ,
alignItems: 'center' ,
gap: '4px' ,
color: isPositive ? '#22c55e' : '#ef4444'
} } >
{ isPositive ? < TrendingUp size = { 16 } /> : < TrendingDown size = { 16 } /> }
< Typography as = "p" size = "text-sm" weight = "medium" >
{ change } %
</ Typography >
</ div >
</ div >
< Typography as = "p" size = "text-xs" style = { { color: '#666' } } >
vs. last month
</ Typography >
</ CardContainer >
);
}
// Usage
< div style = { { display: 'grid' , gridTemplateColumns: 'repeat(3, 1fr)' , gap: '16px' } } >
< StatsCard title = "Total Bids" value = "1,234" change = "12.5" isPositive = { true } />
< StatsCard title = "Active Auctions" value = "89" change = "5.2" isPositive = { true } />
< StatsCard title = "Won Auctions" value = "23" change = "-2.1" isPositive = { false } />
</ div >
Profile Card
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
import { Button } from '@/shared/ui/button/Button' ;
import { User , Mail , MapPin } from 'lucide-react' ;
function ProfileCard ({ user }) {
return (
< CardContainer >
< div style = { { display: 'flex' , gap: '16px' , marginBottom: '16px' } } >
< img
src = { user . avatarUrl }
alt = { user . name }
style = { { width: '64px' , height: '64px' , borderRadius: '50%' } }
/>
< div >
< Typography as = "h3" weight = "bold" >
{ user . name }
</ Typography >
< Typography as = "p" size = "text-sm" style = { { color: '#666' } } >
Member since { user . joinDate }
</ Typography >
</ div >
</ div >
< div style = { { display: 'flex' , flexDirection: 'column' , gap: '8px' , marginBottom: '16px' } } >
< div style = { { display: 'flex' , alignItems: 'center' , gap: '8px' } } >
< Mail size = { 16 } color = "#666" />
< Typography as = "p" size = "text-sm" >
{ user . email }
</ Typography >
</ div >
< div style = { { display: 'flex' , alignItems: 'center' , gap: '8px' } } >
< MapPin size = { 16 } color = "#666" />
< Typography as = "p" size = "text-sm" >
{ user . location }
</ Typography >
</ div >
</ div >
< Button variant = "outline" block >
View Full Profile
</ Button >
</ CardContainer >
);
}
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
import { TextField } from '@/shared/ui/TextField/TextField' ;
import { Button } from '@/shared/ui/button/Button' ;
function LoginCard () {
return (
< CardContainer >
< Typography as = "h2" weight = "bold" style = { { marginBottom: '8px' } } >
Welcome Back
</ Typography >
< Typography as = "p" size = "text-sm" style = { { color: '#666' , marginBottom: '24px' } } >
Sign in to your account to continue
</ Typography >
< form style = { { display: 'flex' , flexDirection: 'column' , gap: '16px' } } >
< TextField
label = "Email"
type = "email"
placeholder = "[email protected] "
/>
< TextField
label = "Password"
type = "password"
placeholder = "Enter your password"
/>
< Button type = "submit" variant = "primary" block >
Sign In
</ Button >
< Typography as = "p" size = "text-xs" style = { { textAlign: 'center' , color: '#666' } } >
Don't have an account? < a href = "/signup" > Sign up </ a >
</ Typography >
</ form >
</ CardContainer >
);
}
Card Grid Layout
import CardContainer from '@/shared/ui/CardContainer/CardContainer' ;
import Typography from '@/shared/ui/Typography/Typography' ;
function CardGrid ({ items }) {
return (
< div style = { {
display: 'grid' ,
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))' ,
gap: '24px' ,
padding: '24px'
} } >
{ items . map (( item ) => (
< CardContainer key = { item . id } onClick = { () => handleItemClick ( item . id ) } >
< Typography as = "h3" weight = "medium" >
{ item . title }
</ Typography >
< Typography as = "p" size = "text-sm" style = { { color: '#666' } } >
{ item . description }
</ Typography >
</ CardContainer >
)) }
</ div >
);
}
TypeScript Types
type CardContainerProps = {
children : React . ReactNode ;
onClick ?: ( ... args : any []) => void ;
};
Styling
The CardContainer uses CSS Modules for styling (CardContainer.module.css). The default styles provide:
Consistent padding and border radius
Box shadow for depth
Hover effects (when onClick is provided)
Responsive behavior
Custom Styling
You can wrap the CardContainer and apply custom styles:
< div style = { { maxWidth: '400px' , margin: '0 auto' } } >
< CardContainer >
< h3 > Centered Card </ h3 >
< p > This card is centered with a max width. </ p >
</ CardContainer >
</ div >
Accessibility
When using onClick, consider adding proper keyboard support and ARIA attributes for better accessibility. The current implementation uses a <div> with onClick, which may not be keyboard accessible by default.
Recommended Enhancement
For clickable cards, consider wrapping in a button or link for better accessibility:
< button
onClick = { handleClick }
style = { { all: 'unset' , cursor: 'pointer' , width: '100%' } }
>
< CardContainer >
< h3 > Accessible Clickable Card </ h3 >
</ CardContainer >
</ button >
Best Practices
Use for grouping related content
Cards work best when they contain logically related information. Group form fields, user details, or product information within a single card.
Cards should be scannable. Avoid cramming too much content into a single card. If content is lengthy, consider splitting into multiple cards or using a different layout.
Make clickable cards obvious
If a card has an onClick handler, ensure it has visual affordances (hover effects, cursor change) to indicate it’s interactive.
Use consistent gap spacing when displaying multiple cards in a grid or list. This creates a cleaner, more professional appearance.
Layout Patterns
Single Column
< div style = { { maxWidth: '600px' , margin: '0 auto' , display: 'flex' , flexDirection: 'column' , gap: '16px' } } >
< CardContainer > Card 1 </ CardContainer >
< CardContainer > Card 2 </ CardContainer >
< CardContainer > Card 3 </ CardContainer >
</ div >
Grid Layout
< div style = { { display: 'grid' , gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))' , gap: '16px' } } >
< CardContainer > Card 1 </ CardContainer >
< CardContainer > Card 2 </ CardContainer >
< CardContainer > Card 3 </ CardContainer >
< CardContainer > Card 4 </ CardContainer >
</ div >
Sidebar + Main Content
< div style = { { display: 'grid' , gridTemplateColumns: '300px 1fr' , gap: '24px' } } >
< CardContainer >
{ /* Sidebar content */ }
</ CardContainer >
< div style = { { display: 'flex' , flexDirection: 'column' , gap: '16px' } } >
< CardContainer > { /* Main content 1 */ } </ CardContainer >
< CardContainer > { /* Main content 2 */ } </ CardContainer >
</ div >
</ div >
Typography Use Typography for consistent text styling inside cards
Button Add action buttons to cards for user interactions
TextField Combine with TextField for form-based cards