AspectRatio
Constrain any child element to a specific aspect ratio, preventing layout shift.
AspectRatio wraps Radix UI AspectRatio to constrain child elements — images, videos, embeds — to a fixed width-to-height ratio. The element fills its container width and maintains the ratio as that width changes, eliminating layout shift during image loads.
Installation
npm install @designforge/uiUsage
import { AspectRatio } from "@designforge/ui";
export default function App() {
return (
<AspectRatio ratio={16 / 9}>
<img
src="/hero.jpg"
alt="Hero"
className="h-full w-full rounded-md object-cover"
/>
</AspectRatio>
);
}Examples
Common ratios
{/* 16:9 — widescreen video / hero */}
<AspectRatio ratio={16 / 9} className="bg-muted rounded-lg overflow-hidden">
<img src="/video-thumb.jpg" alt="" className="object-cover w-full h-full" />
</AspectRatio>
{/* 1:1 — square avatar or product image */}
<div className="w-32">
<AspectRatio ratio={1}>
<img src="/avatar.jpg" alt="" className="rounded-full object-cover w-full h-full" />
</AspectRatio>
</div>
{/* 4:3 — classic photo */}
<AspectRatio ratio={4 / 3}>
<img src="/photo.jpg" alt="" className="object-cover w-full h-full" />
</AspectRatio>
{/* 21:9 — cinematic banner */}
<AspectRatio ratio={21 / 9} className="bg-muted">
<img src="/banner.jpg" alt="" className="object-cover w-full h-full" />
</AspectRatio>Video embed
Wrapping an iframe in AspectRatio keeps the embed proportional across all viewport widths.
<AspectRatio ratio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full h-full rounded-md"
/>
</AspectRatio>Skeleton placeholder
Use AspectRatio as the shell for a loading skeleton so the page layout stays stable while data is fetching.
import { Skeleton } from "@designforge/ui";
<AspectRatio ratio={16 / 9}>
<Skeleton className="w-full h-full rounded-md" />
</AspectRatio>Constrained width
Wrap in a sized container to control the rendered width without touching AspectRatio itself — it always fills its parent.
<div className="max-w-sm">
<AspectRatio ratio={4 / 3} className="rounded-lg overflow-hidden">
<img src="/card-image.jpg" alt="Card" className="object-cover w-full h-full" />
</AspectRatio>
</div>All common ratios side by side
const ratios = [
{ ratio: 16 / 9, label: "16:9" },
{ ratio: 4 / 3, label: "4:3" },
{ ratio: 1, label: "1:1" },
{ ratio: 9 / 16, label: "9:16" },
];
<div className="grid grid-cols-2 gap-4">
{ratios.map(({ ratio, label }) => (
<AspectRatio key={label} ratio={ratio} className="bg-muted rounded-md flex items-center justify-center">
<span className="text-sm font-semibold text-muted-foreground">{label}</span>
</AspectRatio>
))}
</div>API Reference
<AspectRatio>
| Prop | Type | Default | Description |
|---|---|---|---|
ratio | number | 1 | Width divided by height (e.g. 16/9, 4/3, 1). |
className | string | — | Additional CSS classes on the outer wrapper element. |
The component is implemented using a padding-top trick — the wrapper has position: relative and the child is stretched to fill it with position: absolute; inset: 0. This means the child must be sized to width: 100%; height: 100% (or equivalent Tailwind classes w-full h-full) to fill the constrained area correctly.
Live View
Here is a live contextual rendering of the component directly from our isolated Storybook environment.