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

PropTypeDefaultDescription
focusKeystringrequiredUnique identifier
focusablebooleanfalseMake card itself focusable
trackChildrenbooleanfalseTrack child focus
saveLastFocusedChildbooleanfalseRestore child focus
classNamestring-CSS classes
activestring-CSS classes when focused
onEnterPressfunction-Enter press handler
onFocusfunction-Focus handler
childrenReactNode | 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