---
title: Aspect Ratio
description: A box that keeps its shape and fills whatever is put in it. CSS aspect-ratio, as a prop.
source: https://adamjurek.com/components/aspect-ratio
---

## Home


`AspectRatio` is a box that holds a width-to-height ratio and stretches its
contents to fill it edge to edge. Reach for it wherever an image, video, or
embed needs a fixed shape before it loads, so the layout does not jump.

<!-- ::start:showcase demo="aspect-ratio" height="380" -->
<!-- ::end:showcase -->

## Why it is built this way

CSS `aspect-ratio` does the entire job; the component exists only so the
number arrives as a prop instead of an inline style somebody has to remember
the syntax for. The default is 16/9, and whatever is passed as children fills
the box edge to edge without extra styling of its own.

## What it does not do

It takes one ratio, not a responsive set. A box that needs 4/3 on a phone and
16/9 on a desktop wraps two of these behind a media query, or computes the
number itself - this component does not read breakpoints.

> [!NOTE] Install commands are not written here
> Anything in `packages/ui/registry.ts` gets its TanStack and shadcn commands
> attached automatically, so there is nothing to keep in sync.


## Install

<!-- ::start:tabs -->

### TanStack

```shell
tanstack add https://adamjurek.com/r/tanstack/aspect-ratio.json
```

### shadcn

```shell
pnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/aspect-ratio.json
```

### pnpm

```shell
pnpm add @sushindustries/ui @sushindustries/atoms
```

<!-- ::end:tabs -->

## What you get

| | |
| --- | --- |
| Version | 0.1.0 |
| Category | layout · Page structure |
| Files | `aspect-ratio.tsx` |
| Dependencies | None |
| Tags | media, no-deps |

> [!NOTE] No runtime dependencies
> It brings nothing with it beyond the stylesheet.

## Get Started


Install commands are on Home, attached from the registry - they are not written
here, because a second copy is a copy that goes stale. This tab starts after the
install worked.

## Use it

```tsx
import { AspectRatio } from "@sushindustries/ui";

export function Example() {
	return (
		<AspectRatio ratio={16 / 9}>
			<img src="/hero.jpg" alt="" />
		</AspectRatio>
	);
}
```

## What you should see

A box exactly 16:9, whatever width its parent gives it - the image inside
fills it edge to edge and crops rather than stretching, because the child
is absolutely positioned over the box. Resize the parent and the box
resizes with it, keeping the ratio, without any JavaScript running.

## If nothing happens

If the box collapses to zero height, the parent has no width to compute
from - `aspect-ratio` needs one axis to derive the other, and a parent
with `width: 0` or `display: contents` gives it nothing to work with. If
the child overflows the box instead of filling it, check it is a direct
child - the stylesheet only positions immediate children, not anything
nested deeper inside them.


## Guides


## Composing it

The box needs a definite width from its parent - a grid cell, a flex item
with `flex: 1`, a fixed-width container - because `aspect-ratio` computes
height from width, not the other way round. A parent with no width
resolves to a box with no height, which reads as "the component renders
nothing" when the actual cause is one level up.

## Every direct child fills the box

`.ratio > *` positions every direct child absolutely, not only the first,
so passing more than one child stacks them on top of each other rather
than laying them out side by side. That is useful for an image with a
caption overlay; it is a bug if the goal was two things side by side,
which needs its own flex wrapper around them first.

## When not to use it

For a single known image at a known size, a plain `<img>` with `width`
and `height` attributes gets the same layout stability from the browser's
own intrinsic-size reservation, with no wrapper element. Reach for this
component when the ratio has to hold regardless of what loads into it -
a video, an iframe, an image whose real dimensions are not known ahead of
time.


## API


<!-- generated:api -->

## Props

| Prop | Type | Default | Does |
| --- | --- | --- | --- |
| `ratio?` | `number` | `16 / 9` | Width over height: 16/9, 1, 4/3. |
| `children` | `ReactNode` | - |  |

<!-- /generated:api -->

## Notes

`ratio` takes any positive number, not a preset - `16 / 9`, `1`, `4 / 3`
are conventions in this codebase, not values the component checks for.
Passing a fraction the wrong way round (`9 / 16` for a landscape image)
produces a tall box rather than an error, since the component has no way
to know which orientation was intended.


## Examples


Examples are the tab where the component is shown doing a job, not
demonstrating a prop. The API tab already lists the props.

<!-- ::start:showcase demo="aspect-ratio" height="420" -->
<!-- ::end:showcase -->

Press Compare. The frames are real viewports, so a layout that breaks at 320
breaks here too rather than in somebody's hands.

## In a page

```tsx
import { AspectRatio, Card } from "@sushindustries/ui";

export function VideoCard({ src, poster }: { src: string; poster: string }) {
	return (
		<Card title="Product walkthrough">
			<AspectRatio ratio={16 / 9}>
				<video src={src} poster={poster} controls />
			</AspectRatio>
		</Card>
	);
}
```

## What this example is not

`ratio={16 / 9}` here is fixed at build time, but nothing about the
component requires that - it reads just as well from a prop passed down
from a CMS field, as long as the value is a number by the time it
reaches `AspectRatio`.
