Skip to main content
Gremorie

Aspect Ratio

Reserve responsive vertical space so media never causes layout shift.

Overview

AspectRatio is a one-prop layout primitive that locks a box to a numeric ratio at every viewport width. Drop it around any image, iframe, video embed, or skeleton and the surrounding layout reserves the right slot before the asset paints, eliminating Cumulative Layout Shift hits.

Reach for AspectRatio whenever the height of a child is derived from its width: hero covers, card thumbnails, oEmbed-style video, map iframes, OG image previews. For fixed-pixel media or short-lived skeletons where you already know both dimensions, plain CSS is enough.

Preview

16 : 9

Installation

bash npx gremorie@latest add rx-aspect-ratio

bash pnpm dlx gremorie@latest add rx-aspect-ratio

bash yarn dlx gremorie@latest add rx-aspect-ratio

bash bunx --bun gremorie@latest add rx-aspect-ratio

Usage

import { AspectRatio } from "@gremorie/rx-containers";

export function Cover() {
  return (
    <AspectRatio ratio={16 / 9} className="bg-muted">
      <img
        src="/cover.jpg"
        alt="Cover artwork"
        className="size-full rounded-md object-cover"
      />
    </AspectRatio>
  );
}

Angular edition planned for a follow-up release. The React API will translate one-to-one to a structural directive.

API

<AspectRatio>

Renders a <div> styled by Radix to maintain width / height = ratio at every breakpoint. The child element should size itself with size-full (or width: 100%; height: 100%) so it fills the reserved box.

PropTypeDefaultDescription
rationumber1Aspect ratio expressed as width divided by height. Use 16 / 9 for landscape video, 4 / 3 for legacy video, 1 for square, 3 / 4 for portrait posters.
asChildbooleanfalseWhen true, merges props onto the single child element via Radix Slot instead of rendering an extra div.
...propsReact.ComponentProps<"div">-Standard div attributes including className and style.

Composition

  1. <AspectRatio> wraps a single child and reserves the ratio.
  2. The child (image, iframe, video, skeleton) should fill the reserved box: className="size-full object-cover" on <img>, className="size-full" on iframes and videos.
  3. Inside a Card, place AspectRatio at the top of <CardContent> so the cover stays flush with the card border.

The wrapper handles the math via padding-bottom plus absolute positioning. Children render inside a stacked context, so absolute overlays (badges, gradients) compose naturally without extra wrappers.

Variations

Video cover with object-fit

16 : 9 video slot

<AspectRatio ratio={16 / 9} className="overflow-hidden rounded-md bg-muted">
  <img
    src="/poster.jpg"
    alt="Episode 12 poster"
    className="size-full object-cover"
  />
</AspectRatio>

Use object-cover on <img> to crop edges that exceed the reserved ratio. Use object-contain instead when the image must be visible in full, even if it leaves whitespace inside the box.

Square avatar tile

1 : 1

<AspectRatio ratio={1} className="rounded-full bg-muted">
  <img src="/avatar.jpg" alt="Mila Tasic" className="size-full object-cover" />
</AspectRatio>

Use ratio={1} plus rounded-full for avatar tiles that scale fluidly inside grid cells.

Skeleton placeholder

<AspectRatio ratio={16 / 9} className="rounded-md">
  <Skeleton className="size-full" />
</AspectRatio>

Use the same ratio on the loading skeleton and the final asset so there is zero layout shift when the asset paints. See Skeleton.

Accessibility

  • Presentation only: AspectRatio is a layout primitive and renders no role of its own. Any semantics belong to the child (<img>, <iframe>, <video>).
  • Always label media: pass a real alt to images, a title to iframes, and aria-label to videos. Empty alt="" is fine only for decorative covers that pair with adjacent text.
  • No keyboard surface: the wrapper is not focusable. Focusable children (links, video controls) keep their native tab order.
  • No motion injected: the primitive adds no animation, so users with prefers-reduced-motion see whatever the child renders.
  • Card - the most common host for an AspectRatio cover.
  • Skeleton - pair with AspectRatio to reserve space during fetch.
  • ScrollArea - sibling primitive for horizontally scrolling media strips.

On this page