# Aspect Ratio

A box that holds a width-to-height ratio at any width and eases between ratios when you change it, for media, embeds and placeholders.

**Category:** Display  
**Exports:** `AspectRatio`  
**npm dependencies:** motion (+ clsx, tailwind-merge for `cn`)

## Install

```bash
npx shadcn@latest add https://charcoalui.com/r/aspect-ratio.json
```
The file lands at `components/ui/aspect-ratio.tsx`; import from `@/components/ui/aspect-ratio`.

## API — Aspect Ratio
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `ratio` | `number` | `16 / 9` |  |
| `children` | `ReactNode` | — | Filled to the box. |
| `className` | `string` | — |  |

## Example — Aspect Ratio

```tsx
import * as React from "react";
import { AspectRatio } from "@/components/ui/aspect-ratio";

const ratios = { "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "3:4": 3 / 4 };

export default function AspectRatioDemo() {
  const [r, setR] = React.useState<keyof typeof ratios>("16:9");
  return (
    <div className="w-full max-w-xs">
      <AspectRatio ratio={ratios[r]} className="rounded-xl border">
        <img src="https://charcoalui.com/demo/ridge.webp" alt="Mountain ridge at dusk" className="object-cover" />
      </AspectRatio>
      <div className="mt-3 flex justify-center gap-1">
        {(Object.keys(ratios) as (keyof typeof ratios)[]).map((k) => (
          <button key={k} type="button" onClick={() => setR(k)} className={`cursor-pointer rounded-lg px-2.5 py-1 font-mono text-xs ${r === k ? "bg-accent font-medium" : "text-muted-foreground"}`}>
            {k}
          </button>
        ))}
      </div>
    </div>
  );
}
```

## Source — components/ui/aspect-ratio.tsx

```tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";

export type AspectRatioProps = {
  /** Width divided by height, e.g. 16 / 9. */
  ratio?: number;
  children: React.ReactNode;
  className?: string;
};

/** A box that holds a width-to-height ratio at any width, and eases between ratios when you change it. */
export function AspectRatio({ ratio = 16 / 9, children, className }: AspectRatioProps) {
  const reduce = useReducedMotion();
  return (
    <motion.div
      className={cn("relative w-full overflow-hidden [&>*]:absolute [&>*]:inset-0 [&>*]:size-full", className)}
      initial={false}
      animate={{ aspectRatio: ratio }}
      transition={reduce ? { duration: 0 } : { type: "spring", stiffness: 260, damping: 30 }}
    >
      {children}
    </motion.div>
  );
}
```