Aspect Ratio
Displays content within a desired ratio using CSS aspect-ratio or a JavaScript fallback.
Installation
npx shadcn@latest add aspect-ratio
Usage
import Image from "next/image"
import { AspectRatio } from "@/components/ui/aspect-ratio"
export function AspectRatioDemo() {
return (
<AspectRatio ratio={16 / 9} className="bg-muted">
<Image
src="/placeholder.jpg"
alt="Image"
fill
className="rounded-md object-cover"
/>
</AspectRatio>
)
}
Examples
Square (1:1)
Portrait (3:4)
Multiple Ratios
API Reference
AspectRatio
Maintains a consistent width-to-height ratio for content.
Prop | Type | Default | Description |
---|---|---|---|
ratio | number | 1 | The desired aspect ratio (width/height). |
className | string | - | Additional CSS classes for styling. |
children | React.ReactNode | - | The content to display within the aspect ratio container. |
Implementation Details
Architecture
Built on Radix UI's AspectRatio primitive:
- CSS First: Uses modern CSS
aspect-ratio
property when supported - JavaScript Fallback: Automatically falls back to JavaScript implementation for older browsers
- Responsive: Maintains ratio across all screen sizes
- Flexible: Works with any content type (images, videos, divs, etc.)
How It Works
The AspectRatio component calculates the appropriate height based on the container width and desired ratio:
// Example: 16:9 ratio
<AspectRatio ratio={16 / 9}>
{/* Content automatically sized to maintain 16:9 ratio */}
</AspectRatio>
Common Ratio Values
Ratio | Expression | Use Case |
---|---|---|
Square | 1 / 1 | Profile pictures, thumbnails |
Portrait | 3 / 4 | Vertical photos, mobile screens |
Landscape | 4 / 3 | Traditional photos, presentations |
Widescreen | 16 / 9 | Modern videos, hero images |
Cinematic | 21 / 9 | Ultra-wide banners, movie clips |
Golden Ratio | 1.618 / 1 | Aesthetically pleasing layouts |
Best Practices
Image Implementation
Always use fill
and object-cover
with Next.js Image components:
<AspectRatio ratio={16 / 9} className="bg-muted overflow-hidden rounded-lg">
<Image
src="/image.jpg"
alt="Description"
fill
className="object-cover"
/>
</AspectRatio>
Responsive Considerations
The component maintains its ratio at all screen sizes. For responsive ratios, use CSS classes:
<AspectRatio
ratio={16 / 9}
className="md:aspect-[21/9] lg:aspect-[32/9]"
>
<div>Responsive content</div>
</AspectRatio>
Performance Tips
- Image Optimization: Always use optimized images with appropriate dimensions
- Lazy Loading: Combine with Next.js Image lazy loading for better performance
- Container Sizing: Set explicit max-width on containers for predictable layouts
Common Use Cases
Video Embeds
Perfect for maintaining video aspect ratios:
<AspectRatio ratio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full h-full rounded-lg"
/>
</AspectRatio>
Card Thumbnails
Consistent thumbnail sizes in grid layouts:
<div className="grid grid-cols-3 gap-4">
{images.map((image) => (
<AspectRatio key={image.id} ratio={1} className="bg-muted rounded-lg">
<Image src={image.src} alt={image.alt} fill className="object-cover" />
</AspectRatio>
))}
</div>
Hero Sections
Wide banner content with consistent proportions:
<AspectRatio ratio={21 / 9} className="bg-gradient-to-r from-blue-600 to-purple-600">
<div className="flex items-center justify-center h-full text-white">
<div className="text-center">
<h1 className="text-4xl font-bold">Hero Content</h1>
<p className="text-lg opacity-90">Perfectly proportioned banner</p>
</div>
</div>
</AspectRatio>
Dashboard Widgets
Consistent widget sizing in dashboard layouts:
<AspectRatio ratio={4 / 3} className="bg-card border rounded-lg p-4">
<div className="h-full flex flex-col">
<h3 className="font-semibold mb-2">Widget Title</h3>
<div className="flex-1 flex items-center justify-center text-muted-foreground">
Chart or content goes here
</div>
</div>
</AspectRatio>
Accessibility
Image Alt Text
Always provide meaningful alt text for images:
<AspectRatio ratio={16 / 9}>
<Image
src="/product.jpg"
alt="Blue wireless headphones on a wooden desk with soft lighting"
fill
className="object-cover"
/>
</AspectRatio>
Interactive Content
Ensure interactive elements remain accessible within the aspect ratio container:
<AspectRatio ratio={16 / 9} className="bg-muted rounded-lg">
<button
className="w-full h-full flex items-center justify-center hover:bg-accent transition-colors"
aria-label="Play video"
>
<PlayIcon className="h-12 w-12 text-primary" />
</button>
</AspectRatio>
Browser Support
- Modern browsers: Uses CSS
aspect-ratio
property - Legacy browsers: Automatic JavaScript fallback via Radix UI
- Mobile: Full support across all mobile browsers
- Progressive Enhancement: Gracefully degrades in unsupported environments
Troubleshooting
Common Issues
- Images not displaying: Ensure
fill
prop is used with Next.js Image - Overflow issues: Add
overflow-hidden
to the AspectRatio className - Incorrect ratios: Double-check ratio calculation (width / height)
- Layout shifts: Set explicit container widths to prevent CLS
Debugging Tips
// Add visual debugging
<AspectRatio
ratio={16 / 9}
className="bg-red-100 border-2 border-red-500"
>
<div className="bg-blue-100 h-full w-full flex items-center justify-center">
Debug: {16/9} ratio
</div>
</AspectRatio>
Integration Examples
With React Hook Form
function ImageUploadField() {
const { register, watch } = useForm()
const watchedImage = watch("image")
return (
<AspectRatio ratio={16 / 9} className="bg-muted border-2 border-dashed rounded-lg">
{watchedImage ? (
<Image src={watchedImage} alt="Upload preview" fill className="object-cover" />
) : (
<div className="flex items-center justify-center h-full text-muted-foreground">
<UploadIcon className="h-8 w-8 mb-2" />
<p>Upload an image</p>
</div>
)}
<input {...register("image")} type="file" className="sr-only" />
</AspectRatio>
)
}
With State Management
function ImageCarousel({ images, currentIndex }) {
return (
<AspectRatio ratio={4 / 3} className="bg-muted rounded-lg overflow-hidden">
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="h-full w-full"
>
<Image
src={images[currentIndex]}
alt={`Image ${currentIndex + 1}`}
fill
className="object-cover"
/>
</motion.div>
</AnimatePresence>
</AspectRatio>
)
}
SEO Considerations
- Image Optimization: Proper aspect ratios improve Core Web Vitals
- Lazy Loading: Better performance metrics with Next.js Image
- Structured Data: Use appropriate schema markup for images
- Alt Text: Descriptive alt attributes improve accessibility and SEO
- Responsive Images: Serve appropriate sizes for different devices