# Aspect ratio

> [llms.txt](https://zriz.dev/llms.txt)

Buttons that lock the crop to a ratio such as 1:1 or 4:5, and one that swaps portrait and landscape.

The page at https\://zriz.dev/docs/aspect-ratio runs this cropper: a 3:2 photo, a track of free, original, 1:1, 4:5 and 16:9 with an orientation button, and open, reset and download in the corners.

## Anatomy

The group and the orientation button go anywhere inside `Root`. Each item names one ratio:

```tsx
<Cropper.AspectGroup aria-label="Aspect ratio">
  <Cropper.AspectItem value="free">Free</Cropper.AspectItem>
  <Cropper.AspectItem value={1}>1:1</Cropper.AspectItem>
  <Cropper.AspectItem value={4 / 5}>4:5</Cropper.AspectItem>
  <Cropper.AspectItem value={16 / 9}>16:9</Cropper.AspectItem>
</Cropper.AspectGroup>
<Cropper.Orientation aria-label="Swap orientation">
  <svg
    aria-hidden="true"
    width={20}
    height={20}
    viewBox="0 0 20 20"
  >
    <path
      d="M3 6h14v8H3z"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.5}
    />
  </svg>
</Cropper.Orientation>
```

`value` is width divided by height, so `4 / 5` is 4:5. `"free"` drops the lock and `"original"` uses the photo's own ratio. Picking an item springs the crop to it, with `data-transition="morph"` on `Root`.

On a round crop every item but `value={1}` is disabled. zriz ships these parts bare. The demo above adds `"original"`, turns its labels with the orientation as the ratios further down do, and uses the styles from the zriz site.

## Lock one ratio with no buttons

Pass `aspect` to `Root` and leave out the group. The crop keeps that ratio, and `Reset` returns to it:

```tsx
<Cropper.Root src={photo} aspect={16 / 9}>
  <Cropper.Viewport style={{ height: 400 }}>
    <Cropper.Image />
    <Cropper.Mask />
    <Cropper.Area aria-label="Crop area" />
  </Cropper.Viewport>
</Cropper.Root>
```

## Use an item outside a group

Outside `AspectGroup`, an item is a plain button with `aria-pressed` instead of a radio:

```tsx
<Cropper.AspectItem value={1}>Square</Cropper.AspectItem>
```

## Turn the orientation icon

`Orientation` says which way the crop faces in `data-orientation`. This turns its icon for portrait:

```css
[data-zriz-item="orientation"] {
  &[data-orientation="portrait"] svg {
    rotate: 90deg;
  }
}
```

A press swaps the ratio for its inverse, so 4:5 becomes 5:4. It springs like an item pick, and `onValueChange` gets the reason `orientation`.

## Follow the orientation in the labels

After a swap the crop is 5:4, so a 4:5 item is no longer checked. To keep one item per ratio, flip the values and labels with the orientation. Render it inside `Root`:

```tsx
"use client";

import { useCropperState } from "@zriz/react";
import * as Cropper from "@zriz/react/cropper";

const RATIOS = [
  [4, 5],
  [3, 4],
  [9, 16],
] as const;

export const Ratios = () => {
  const aspect = useCropperState((s) => s.aspect);
  const wide = typeof aspect === "number" && aspect > 1;

  return (
    <Cropper.AspectGroup aria-label="Aspect ratio">
      <Cropper.AspectItem value="free">Free</Cropper.AspectItem>
      <Cropper.AspectItem value={1}>1:1</Cropper.AspectItem>
      {RATIOS.map(([w, h]) => (
        <Cropper.AspectItem
          key={`${w}:${h}`}
          value={wide ? h / w : w / h}
        >
          {wide ? `${h}:${w}` : `${w}:${h}`}
        </Cropper.AspectItem>
      ))}
    </Cropper.AspectGroup>
  );
};
```

## Keyboard

The group is one tab stop. Tab lands on the checked item, or on the first one when none is checked. The arrows move and select at once:

| Key | What it does |
| - | - |
| `right` or `down` | Select the next ratio |
| `left` or `up` | Select the previous ratio |
| `home` or `end` | Select the first or the last ratio |

The arrows wrap around at the ends and skip disabled items. On a right-to-left page or with `dir="rtl"` on `Root`, left and right swap.

## API

### `AspectGroup`

Renders a `<div role="radiogroup">` anywhere inside `Root`. Every part also takes `className`, `render` and the props of its element, see [composition](https://zriz.dev/docs/composition.md). `className` and `render` get `{ aspect }`.

- `aria-label` `string`

  Names the group for screen readers

### `AspectItem`

Renders a `<button type="button">`, with `role="radio"` inside `AspectGroup`. `className` and `render` get `{ checked, disabled }`.

- `value` `"free" | "original" | number` required

  The ratio this item picks, as width / height

| Attribute | When |
| - | - |
| `data-checked` | While this ratio is the current one |
| `data-disabled` | On a round crop for every value but 1, or when you pass `disabled` to the item |

### `Orientation`

Renders a `<button type="button">` anywhere inside `Root`. It has no props of its own. It is `disabled` while the aspect is 1, `"free"` or `"original"`. `className` and `render` get `{ orientation }`.

| Attribute | When |
| - | - |
| `data-orientation` | `"landscape"` above 1, `"portrait"` below 1, `"square"` at 1 and while the aspect is `"free"` or `"original"` |
| `data-disabled` | While the aspect is 1, `"free"` or `"original"` |
