--primitiv-aspect-ratioA box that constrains its content to a width-to-height ratio via CSS aspect-ratio. ratio is a curated preset set of modifier classes (1/1, 4/3, 3/2, 16/9, 21/9 and their portrait inverses); a bespoke ratio comes from overriding --primitiv-aspect-ratio in a stylesheet.
primitiv add aspect-ratio installs it whichever mode you are reading.Playground
Preview
import { AspectRatio } from "@/components/ui/aspect-ratio";
<AspectRatio ratio="1/1"> <img src="/cover.jpg" alt="" /></AspectRatio>Density is set by a data-density ancestor — the Context system, not a AspectRatio prop.
Installation
npx primitiv add aspect-ratiopnpm dlx primitiv add aspect-ratioyarn dlx primitiv add aspect-ratiobunx primitiv add aspect-ratioImport
import { AspectRatio } from "@/components/ui/aspect-ratio";No headless primitive — this one ships only as a copied styled surface, so primitiv add is the only way in, whichever mode you are reading.
Props
AspectRatio
Extends HTMLDivElement — every native attribute of that element is accepted and forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
| ratio | "1/1" | "4/3" | "3/2" | "16/9" | "21/9" | "3/4" | "2/3" | "9/16" | 1/1 | Width-to-height ratio, from a curated preset set. A bespoke ratio is set by overriding the --primitiv-aspect-ratio custom property in your own stylesheet — the component writes no inline styles. |
Styling contract
Accessibility
- AspectRatio is a plain
<div>with no semantics of its own — it shapes the box, and the child inside it carries the meaning. An image inside still needs its ownalt. - It prevents layout shift: because the box reserves its height from the ratio before the image loads, the content below it does not jump — a real accessibility win for anyone who has started reading when the image arrives.
- Nothing here is interactive or focusable; it is a presentational wrapper, so it adds no keyboard or ARIA surface of its own.
Examples
Framing media
The usual job: wrap an image or video so it holds a shape while the column around it resizes — no layout shift as it loads, because the box already has its height. 16/9 for video, 1/1 for an avatar or thumbnail. The child fills the frame; give an <img> object-fit: cover so it crops rather than distorts.
import { AspectRatio } from "@/components/ui/aspect-ratio";
<AspectRatio ratio="16/9"> <img src="/cover.jpg" alt="Team offsite" style={{ inlineSize: "100%", blockSize: "100%", objectFit: "cover" }} /></AspectRatio>Portrait and landscape
The presets come in both orientations — 4/3, 3/2, 16/9, 21/9 landscape and their inverses 3/4, 2/3, 9/16 portrait — so a card grid can hold a consistent shape whichever way the source is oriented. They are a closed set of modifier classes, which is what keeps them correct in server-rendered markup with no inline style.
import { AspectRatio } from "@/components/ui/aspect-ratio";
<AspectRatio ratio="1/1">{/* square */}</AspectRatio><AspectRatio ratio="4/3">{/* classic landscape */}</AspectRatio><AspectRatio ratio="9/16">{/* portrait / stories */}</AspectRatio>A custom ratio
The presets are the common cases, not a limit. For a bespoke ratio, set --primitiv-aspect-ratio in your own stylesheet — the modifier classes only ever set that one custom property, so overriding it is the supported escape hatch rather than a fork.
import { AspectRatio } from "@/components/ui/aspect-ratio";
{/* 5 : 2 banner — override the one custom property. */}<AspectRatio style={{ "--primitiv-aspect-ratio": "5 / 2" }}> <img src="/banner.jpg" alt="" /></AspectRatio>