Aspect ratio
Buttons that lock the crop to a ratio such as 1:1 or 4:5, and one that swaps portrait and landscape.
Anatomy
The group and the orientation button go anywhere inside Root. Each item names one ratio:
<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:
<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:
<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:
[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:
"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 |
|---|---|
| rightordown | Select the next ratio |
| leftorup | Select the previous ratio |
| homeorend | 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. 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" |