A React implementation of the elegant Apple TV card effect with 3D rotation and parallax movement. This component is designed to work seamlessly with React and Next.js applications.
- ✨ Smooth 3D rotation effect on hover
- 🖼️ Beautiful parallax movement with depth layers
- 🎛️ Customizable intensity and rotation limits
- 🌟 Reflection and shadow effects
- 🚀 Built with Framer Motion for performant animations
- 📱 Responsive and mobile-friendly
- 🧩 Full TypeScript support
- ♿ Accessibility features with reduced motion support
- ⚡ Next.js ready (works with both Pages and App Router)
npm install react-apple-tv-card
# or
yarn add react-apple-tv-card
# or
pnpm add react-apple-tv-card
This package requires:
- React 16.8.0 or newer (for Hooks support)
- Framer Motion 6.0.0 or newer
import AppleTVCard from 'react-apple-tv-card';
function MyComponent() {
return (
<AppleTVCard
title="My Awesome Card"
backgroundImage="/path/to/image.jpg"
width={300}
height={170}
>
{/* Optional content with parallax effect */}
<div style={{
width: '5em',
height: '5em',
border: '1em dashed white'
}} />
</AppleTVCard>
);
}
Prop | Type | Default | Description |
---|---|---|---|
title |
string |
undefined |
Title displayed below the card |
backgroundImage |
string |
undefined |
URL for the background image |
width |
number |
300 |
Width of the card in pixels |
height |
number |
auto (16:9) | Height of the card in pixels |
autoSize |
boolean |
false |
Whether to size the card relative to its container |
withShadow |
boolean |
true |
Whether to show dynamic shadow effects |
withReflection |
boolean |
true |
Whether to show reflection effects |
rounded |
boolean |
true |
Whether to use rounded corners |
alwaysShowTitle |
boolean |
false |
Whether to always show the title |
shouldShowTitle |
boolean |
true |
Whether to show the title at all |
maxRotation |
number |
10 |
Maximum rotation angle in degrees |
maxTranslation |
number |
10 |
Maximum translation distance in pixels |
intensity |
number |
1 |
Overall intensity of the 3D effect (0-1) |
showBadge |
boolean |
false |
Whether to show a badge in the top-right corner |
badgeCount |
number |
0 |
The count to display inside the badge |
children |
ReactNode |
undefined |
Content to display with parallax effect |
className |
string |
'' |
Optional CSS class name for the card container |
style |
React.CSSProperties |
{} |
Optional style object for the card container |
onClick |
() => void |
undefined |
Optional callback when the card is clicked |
<AppleTVCard
title="Movie Title"
backgroundImage="/movie-poster.jpg"
width={350}
height={200}
maxRotation={15}
intensity={0.8}
withShadow={true}
withReflection={true}
>
<div style={{
padding: '1rem',
background: 'rgba(0,0,0,0.6)',
color: 'white',
borderRadius: '0.5rem',
textAlign: 'center'
}}>
<h3>Now Playing</h3>
<p>Click to watch</p>
</div>
</AppleTVCard>
<AppleTVCard
title="Click Me"
backgroundImage="/background.jpg"
onClick={() => alert('Card clicked!')}
maxRotation={12}
maxTranslation={8}
>
<button
style={{
padding: '0.5rem 1rem',
border: 'none',
borderRadius: '4px',
background: 'white',
cursor: 'pointer'
}}
>
Play Now
</button>
</AppleTVCard>
<AppleTVCard
title="Product Card"
backgroundImage="/product.jpg"
intensity={0.4}
maxRotation={5}
maxTranslation={3}
withReflection={false}
>
<div className="product-overlay">
<h4>Premium Product</h4>
<span className="price">$99.99</span>
</div>
</AppleTVCard>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '2rem'
}}>
{movies.map(movie => (
<AppleTVCard
key={movie.id}
title={movie.title}
autoSize={true}
backgroundImage={movie.posterUrl}
onClick={() => playMovie(movie.id)}
/>
))}
</div>
<AppleTVCard
title="Accessible Card"
backgroundImage="/image.jpg"
onClick={() => navigate('/details')}
// The component automatically respects prefers-reduced-motion
// and provides keyboard navigation support
>
<div role="button" aria-label="View details">
<span>Learn More</span>
</div>
</AppleTVCard>
<AppleTVCard
title="Notifications"
backgroundImage="/notification-bg.jpg"
width={320}
showBadge={true}
badgeCount={5}
>
<div style={{ color: 'white', fontWeight: 'bold' }}>Inbox</div>
</AppleTVCard>
This package includes comprehensive TypeScript definitions:
import AppleTVCard, { AppleTVCardProps } from 'react-apple-tv-card';
// Type-safe wrapper component
const MovieCard: React.FC<{
movie: Movie;
onPlay: (id: string) => void;
}> = ({ movie, onPlay }) => {
return (
<AppleTVCard
title={movie.title}
backgroundImage={movie.posterUrl}
width={300}
height={169}
onClick={() => onPlay(movie.id)}
maxRotation={12}
intensity={0.9}
>
<div className="movie-overlay">
<span className="duration">{movie.duration}</span>
<span className="rating">{movie.rating}</span>
</div>
</AppleTVCard>
);
};
'use client';
import AppleTVCard from 'react-apple-tv-card';
export default function MyPage() {
return (
<div>
<h1>My Gallery</h1>
<AppleTVCard
title="Next.js Card"
backgroundImage="/my-image.jpg"
/>
</div>
);
}
'use client';
import AppleTVCard from 'react-apple-tv-card';
import Image from 'next/image';
export default function OptimizedCard() {
return (
<AppleTVCard
title="Optimized Image"
width={400}
height={225}
>
<Image
src="/hero-image.jpg"
alt="Hero"
fill
style={{ objectFit: 'cover' }}
priority
/>
</AppleTVCard>
);
}
- Image Optimization: Use appropriately sized images and consider WebP format for better performance
- Reduced Motion: The component automatically respects
prefers-reduced-motion
settings - Intensity Control: Use lower
intensity
values for better performance on mobile devices - Virtualization: For large grids, consider implementing virtualization
- Memory Management: The component automatically cleans up event listeners and motion values
- ✅ Keyboard navigation support (Tab, Enter, Space)
- ✅ Respects
prefers-reduced-motion
preference - ✅ Focus management with visual indicators
- ✅ Screen reader compatible
- ✅ Touch device optimization
- Chrome 88+
- Firefox 87+
- Safari 14+
- Edge 88+
Card not rotating properly: Ensure the parent container has enough space and doesn't have overflow: hidden
set.
Performance issues: Try reducing the intensity
prop or disabling withReflection
on mobile devices.
TypeScript errors: Make sure you have the latest version of @types/react installed.
Contributions are welcome! Please feel free to submit a Pull Request. See our Contributing Guide for details.
See CHANGELOG.md for a detailed list of changes.
MIT © BlakCode
Inspired by the elegant card interactions in Apple's tvOS interface.