Zoom
A zoom slider and step buttons, for people with no wheel or pinch.
Anatomy
Put ZoomThumb inside Zoom. The steps go anywhere inside Root:
<Cropper.ZoomStep factor={0.8} aria-label="Zoom out">
−
</Cropper.ZoomStep>
<Cropper.Zoom
aria-label="Zoom"
style={{ width: 240, height: 24 }}
>
<Cropper.ZoomThumb />
</Cropper.Zoom>
<Cropper.ZoomStep factor={1.25} aria-label="Zoom in">
+
</Cropper.ZoomStep>zriz paints nothing here and gives these parts no size. Give Zoom a width and a height, then style the track and the thumb. A press on the track jumps the zoom to that point. Full editor paints a track and a thumb you can copy.
The demo above stops the zoom at 3× with maxZoom on Root, marks a step at the limit as shown below and uses the styles from the zriz site.
Centre the thumb
zriz sets the thumb's inset-inline-start on a log scale, so 2× sits 26% along a 15× track. This centres a round thumb on that point:
[data-zriz-item="zoom-thumb"] {
top: 4px;
width: 16px;
height: 16px;
border-radius: 50%;
translate: -50% 0;
background: #111;
}
@media (forced-colors: active) {
[data-zriz-item="zoom-thumb"] {
background: CanvasText;
}
}In a right-to-left layout, use translate: 50% 0. Forced colours mode drops a plain background, so the second rule paints the thumb in CanvasText.
Stop the slider at 5×
Pass the same max to Zoom and ZoomThumb. Pinch still reaches 15×:
<Cropper.Zoom
max={5}
aria-label="Zoom"
style={{ width: 240, height: 24 }}
>
<Cropper.ZoomThumb max={5} />
</Cropper.Zoom>To cap every way of zooming, set maxZoom on Root instead. Both parts follow it.
Mark a step at the limit
At 1× and at maxZoom, a press does nothing, but the button stays enabled. A large minSize can stop the zoom below maxZoom, and useZoom does not report that stop, so this recipe does not mark it. useZoom tells you where the zoom is. Use aria-disabled for this, because disabled drops keyboard focus from the button at the limit. Render this inside Root:
"use client";
import { useZoom } from "@zriz/react";
import * as Cropper from "@zriz/react/cropper";
export const ZoomSteps = () => {
const zoom = useZoom();
return (
<>
<Cropper.ZoomStep
factor={0.8}
aria-label="Zoom out"
aria-disabled={zoom.value <= zoom.min || undefined}
>
−
</Cropper.ZoomStep>
<Cropper.ZoomStep
factor={1.25}
aria-label="Zoom in"
aria-disabled={zoom.value >= zoom.max || undefined}
>
+
</Cropper.ZoomStep>
</>
);
};The browser dims only disabled, so paint the off state yourself:
[data-zriz-item="zoom-step"][aria-disabled="true"] {
opacity: 0.4;
}Build your own slider
useZoom hands a slider of your own the zoom and a commit callback. This one is a range input. Render it inside Root:
"use client";
import { useZoom } from "@zriz/react";
export const ZoomSlider = () => {
const zoom = useZoom();
return (
<input
type="range"
aria-label="Zoom"
min={zoom.min}
max={zoom.max}
step={0.01}
value={zoom.value}
onChange={(e) =>
zoom.onValueChange(e.target.valueAsNumber)
}
onPointerUp={zoom.onValueCommitted}
onPointerCancel={zoom.onValueCommitted}
onKeyUp={zoom.onValueCommitted}
/>
);
};A range input moves in even steps, so 2× sits 7% along a 15× track. Zoom uses a log scale.
Hooks lists every field useZoom returns.
Keyboard
These keys work while Zoom has focus:
| Key | What it does |
|---|---|
| leftorright | Move 1/20 of the track. Right zooms in, left in RTL |
| upordown | Move 1/20 of the track. Up zooms in |
| shift+←↑→↓arrow keys | Move half the track |
| page uporpage down | The same as Shift and an arrow key |
| homeorend | Go to 1× or to max |
Each step multiplies the zoom by the same amount, about 1.14× on a 15× track.
Escape during a drag cancels it and puts the zoom back.
API
Zoom
Renders a <div role="slider"> anywhere inside Root. Every part also takes className, render and the props of its element, see composition. className and render get { dragging }.
- aria-label string
- Names the slider for screen readers. Development builds warn without it
- max number
- The zoom at the end of the track. Defaults to
maxZoomonRoot - format Intl.NumberFormatOptions
- How screen readers hear the zoom. By default 2 reads as
"200%". Anaria-valuetextof yours wins over it
| Attribute | When |
|---|---|
| data-dragging | While a pointer holds any Zoom track of this cropper or a useZoom slider moves, so a second copy lights up too |
ZoomThumb
Renders an aria-hidden <div> inside Zoom. zriz writes position: absolute and inset-inline-start, from 0% at 1× to 100% at max.
- max number
- The zoom at the end of the track. Defaults to
maxZoomonRoot. Keep it equal tomaxonZoom
ZoomStep
Renders a <button type="button"> anywhere inside Root. Each press jumps at once, with no animation and no grid.
- factor number Required
- Multiplies the zoom on each press. Below 1 zooms out, so 0.8 undoes 1.25