Skip to content
Primitiv home
Framework
Consumption mode

Aspect Ratio

stableSource Figma

A 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.

Playground

Density

Preview

1/1
Ratio
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-ratio

Import

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.

PropTypeDefaultDescription
ratio"1/1" | "4/3" | "3/2" | "16/9" | "21/9" | "3/4" | "2/3" | "9/16"1/1Width-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

--primitiv-aspect-ratio

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 own alt.
  • 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.

Density
16 / 9
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.

Density
1/1
4/3
9/16
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.

Density
5 / 2
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>