Card
A versatile container component with built-in focus management, perfect for displaying media content, product cards, and grid items.
Import
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter
} from '@smart-tv/ui';
Basic Usage
<Card focusKey="movie-card" className="w-64">
<img src="/movie-poster.jpg" alt="Movie" className="w-full h-96 object-cover" />
<h3 className="p-4 text-lg font-semibold">Movie Title</h3>
</Card>
With Card Subcomponents
Use the provided subcomponents for structured card layouts.
<Card focusKey="detailed-card" className="w-80">
<CardHeader>
<CardTitle>The Great Adventure</CardTitle>
<CardDescription>2023 • Action • 2h 15m</CardDescription>
</CardHeader>
<CardContent>
<img src="/poster.jpg" alt="Poster" className="w-full h-64 object-cover rounded" />
<p className="mt-4 text-sm text-gray-600">
An epic journey through uncharted territories...
</p>
</CardContent>
<CardFooter className="flex gap-2">
<Button focusKey="play-btn">Play</Button>
<Button focusKey="info-btn">More Info</Button>
</CardFooter>
</Card>
Focusable Card with Actions
<Card
focusKey="action-card"
focusable={true}
className="w-64 transition-transform"
active="scale-105 shadow-2xl ring-4 ring-blue-500"
onEnterPress={() => console.log('Card clicked')}
>
<img src="/content.jpg" alt="Content" className="w-full h-80 object-cover" />
<div className="p-4">
<h3 className="font-bold">Interactive Card</h3>
<p className="text-sm text-gray-600">Press Enter to select</p>
</div>
</Card>
Render Props Pattern
Dynamically render card content based on focus state.
<Card
focusKey="dynamic-card"
className="w-64"
>
{({ focused, focusSelf }) => (
<div className={focused ? 'bg-blue-500 text-white' : 'bg-gray-100'}>
<img
src="/image.jpg"
alt="Image"
className="w-full h-80 object-cover"
/>
<div className="p-4">
<h3 className="font-bold">
{focused ? '▶ Now Playing' : 'Movie Title'}
</h3>
</div>
</div>
)}
</Card>
Card Grid Layout
import { Grid, Card } from '@smart-tv/ui';
<Grid
focusKey="movie-grid"
columns={4}
gap={16}
className="p-8"
>
{movies.map((movie) => (
<Card
key={movie.id}
focusKey={`movie-${movie.id}`}
className="w-full"
active="scale-110 shadow-2xl"
onEnterPress={() => openMovie(movie.id)}
>
<img src={movie.poster} alt={movie.title} className="w-full h-96 object-cover" />
<div className="p-3">
<h3 className="font-semibold">{movie.title}</h3>
<p className="text-sm text-gray-600">{movie.year}</p>
</div>
</Card>
))}
</Grid>
Horizontal Card Row
import { Row, Card } from '@smart-tv/ui';
<Section focusKey="featured-section">
<h2 className="text-2xl font-bold mb-4">Featured Content</h2>
<Row
focusKey="featured-row"
gap={16}
scrollProps={{ behavior: 'smooth' }}
>
{featured.map((item) => (
<Card
key={item.id}
focusKey={`featured-${item.id}`}
className="min-w-[250px]"
active="scale-110"
>
<img src={item.image} alt={item.title} className="w-full h-80 object-cover" />
<p className="p-3 font-semibold">{item.title}</p>
</Card>
))}
</Row>
</Section>
Card with Nested Buttons
Cards can contain nested focusable elements. Use trackChildren
to manage focus.
<Card
focusKey="complex-card"
trackChildren={true}
saveLastFocusedChild={true}
className="w-96"
>
<img src="/movie.jpg" alt="Movie" className="w-full h-80 object-cover" />
<CardContent className="p-4">
<h3 className="text-xl font-bold mb-2">Movie Title</h3>
<p className="text-gray-600 mb-4">Description goes here...</p>
<div className="flex gap-2">
<Button focusKey="play-btn" className="flex-1">
Play
</Button>
<Button focusKey="add-btn" className="flex-1">
Add to List
</Button>
<Button focusKey="info-btn" className="flex-1">
Info
</Button>
</div>
</CardContent>
</Card>
Card with Focus Boundary
<Card
focusKey="modal-card"
isFocusBoundary={true}
focusBoundaryDirections={['up', 'down', 'left', 'right']}
className="w-96 mx-auto"
>
<CardHeader>
<CardTitle>Confirmation</CardTitle>
</CardHeader>
<CardContent>
<p>Are you sure you want to delete this item?</p>
</CardContent>
<CardFooter className="flex gap-2">
<Button focusKey="cancel-btn">Cancel</Button>
<Button focusKey="confirm-btn">Confirm</Button>
{/* Focus cannot escape this card */}
</CardFooter>
</Card>
Card Props
Prop | Type | Default | Description |
---|---|---|---|
focusKey | string | required | Unique identifier |
focusable | boolean | false | Make card itself focusable |
trackChildren | boolean | false | Track child focus |
saveLastFocusedChild | boolean | false | Restore child focus |
className | string | - | CSS classes |
active | string | - | CSS classes when focused |
onEnterPress | function | - | Enter press handler |
onFocus | function | - | Focus handler |
children | ReactNode | RenderFunction | - | Card content |
Subcomponents
CardHeader
Container for card header content (title, description)
CardTitle
Main title of the card
CardDescription
Subtitle or description text
CardContent
Main content area of the card
CardFooter
Footer area for actions and buttons
Best Practices
- Use consistent card dimensions within a grid or row
- Provide clear visual feedback with the
active
prop - Use CardHeader, CardContent, and CardFooter for structured layouts
- Enable
trackChildren
when cards contain buttons - Optimize images for TV resolution (use appropriate sizes)
- Keep card content readable at 10-foot viewing distance