# Aspect ratio

Keeps its content at a fixed width-to-height ratio as the width changes, such as a 16:9 image or video.

- **Import:** `import { AspectRatio } from "@booleanpress/ui/aspect-ratio"`
- **Radix Aspect Ratio:** <https://www.radix-ui.com/primitives/docs/components/aspect-ratio>
- **Page:** <https://ui.booleanpress.com/components/aspect-ratio> · @booleanpress/ui 0.2.0

## Usage

An aspect ratio box reserves the right height before its image or video loads, so the page does not jump.

```tsx
import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

export function Screenshot() {
  return (
    <AspectRatio ratio={16 / 9}>
      <img src="/screenshots/dashboard.png" alt="The delivery dashboard" className="size-full object-cover" />
    </AspectRatio>
  )
}
```

`ratio` is the width divided by the height: `16 / 9`, `4 / 3`, `1` for a square. The box takes the width of its parent and draws nothing of its own; give it a radius, an edge or a fill with `className`, and `overflow-hidden` to clip a rounded image. Its child fills it: use `object-cover` on an image to crop it, `object-contain` to letterbox it.

## Examples

### 16:9 image

A screenshot cropped to 16:9 in a rounded, bordered box.

```tsx
import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

// A drawn chart stands in for a screenshot, so the example needs no network.
const SCREENSHOT =
  "data:image/svg+xml;utf8," +
  encodeURIComponent(
    '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 90"><rect width="160" height="90" fill="#f1f5f9"/><rect x="10" y="10" width="60" height="6" rx="2" fill="#cbd5e1"/><path d="M10 74 L40 52 L64 60 L92 34 L120 42 L150 20" fill="none" stroke="#020617" stroke-width="2"/><path d="M10 74 L40 52 L64 60 L92 34 L120 42 L150 20 L150 80 L10 80 Z" fill="#020617" fill-opacity="0.08"/></svg>'
  )

export default function AspectRatioImage() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="overflow-hidden rounded-lg border">
        <img src={SCREENSHOT} alt="Deliveries per day over the last week, rising" className="size-full object-cover" />
      </AspectRatio>
    </div>
  )
}
```

### Square

`ratio={1}`: three square tiles in a grid.

```tsx
import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

const LOGOS = ["Acme", "Globex", "Initech"]

export default function AspectRatioSquare() {
  return (
    <div className="grid w-full max-w-sm grid-cols-3 gap-3">
      {LOGOS.map((name) => (
        <AspectRatio key={name} ratio={1} className="flex items-center justify-center rounded-lg border bg-subtle">
          <span className="text-sm/normal font-semibold text-muted-foreground">{name}</span>
        </AspectRatio>
      ))}
    </div>
  )
}
```

### Video placeholder

A 16:9 placeholder with a play button, ready for a video player.

```tsx
import { PlayIcon } from "lucide-react"
import { AspectRatio } from "@booleanpress/ui/aspect-ratio"
import { Button } from "@booleanpress/ui/button"

export default function AspectRatioVideo() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="flex flex-col items-center justify-center gap-3 rounded-lg bg-muted">
        <Button size="icon-lg" className="rounded-full" aria-label="Play: connecting your first mail provider">
          <PlayIcon className="fill-current" />
        </Button>
        <span className="text-sm/normal text-muted-foreground">Connecting your first mail provider · 3:12</span>
      </AspectRatio>
    </div>
  )
}
```

## Accessibility

**Semantics.** Two plain `div` elements: an outer one that sets the height and an inner one that holds the content. No role.

**Labels.** The content carries its own name: an image's `alt`, a video's title, a button's label.

**Focus.** Nothing to focus; the content's own controls are in the tab order.

**Known limits.**

- Content taller than the ratio allows is cut off or overflows; the box does not grow.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### AspectRatio

Renders Radix AspectRatio.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element as the inner box instead of a `div`. |
| `ratio` | `number` |  | The width divided by the height. 1 by default. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="aspect-ratio"` (AspectRatio).

## Theming
