# Aspect Ratio Picker

Aspect ratio as a grid of to-scale glyphs: every shape is drawn inside the same square window, so the grid stays aligned and the ratio reads before the label does.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





<ComponentPreview name="aspect-ratio-picker">
  <AspectRatioPickerDemo />
</ComponentPreview>

## Installation [#installation]

```bash
npx shadcn@latest add https://motif-ui.vercel.app/r/aspect-ratio-picker.json
```

## Usage [#usage]

```tsx
import { AspectRatioPicker } from "@/components/ui/aspect-ratio-picker";

export function Frame() {
  const [ratio, setRatio] = useState("16:9");

  return (
    <div className="space-y-2">
      <div className="font-medium text-muted-foreground text-xs">
        Aspect ratio
      </div>
      <AspectRatioPicker
        aria-label="Aspect ratio"
        onChange={setRatio}
        value={ratio}
      />
    </div>
  );
}
```

## The heading is yours [#the-heading-is-yours]

The control draws no label. A field's label belongs to the form around it — above,
beside, in a `<legend>` — and that is layout the caller owns, not something a select
should decide for the page it lands in. So the component renders the grid and nothing
else.

That leaves one line of wiring: a heading you write is plain text, so pass `aria-label`
with the same words and the name reaches a screen reader too.

Pass your own list when the model offers a different set. Five fit a row before the grid
wraps, and `"adaptive"` / `"auto"` swap the glyph for a scan mark:

```tsx
<AspectRatioPicker
  aria-label="Canvas"
  options={["1:1", "16:9", "9:16", "adaptive"]}
  value={ratio}
  onChange={setRatio}
/>
```

## The fixed window [#the-fixed-window]

The thing that makes a row of ratios readable is that nothing in it moves except the
shape. Every glyph is drawn inside a square of the same `size` — 18px here — and the
rectangle inside is scaled to fit that square. A `1:1` and a `16:9` therefore occupy the
same footprint; the eye compares the two rectangles directly instead of comparing two
boxes of different widths.

`showReferenceBox` draws the square itself as a dashed outline. It is on for the selected
cell only, which is what makes the selection read as "this shape, in this frame" rather
than just a highlighted tile.

`AspectRatioIcon` is exported on its own — it is useful anywhere a ratio needs a 16px shape
rather than a string of digits, a trigger button being the obvious one.

<AspectRatioPickerAdaptiveDemo />

## Accessibility [#accessibility]

Each cell is a real `<button>` carrying `aria-pressed`, so the group is reachable and
operable by keyboard and the current choice is announced rather than only coloured in.
The grid is a `role="group"` named by `aria-label`, so the row is announced as one control
rather than as five unrelated buttons. The glyph is `aria-hidden` throughout: the visible
value is the label, and a screen reader should hear `16:9`, not a description of a
rectangle.

## Component source [#component-source]

<ComponentSource name="aspect-ratio-picker" src="registry/new-york/aspect-ratio-picker.tsx" title="aspect-ratio-picker.tsx" />
