# Zoom

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

A zoom slider and step buttons, for people with no wheel or pinch.

The page at https\://zriz.dev/docs/zoom runs this cropper: a 3:2 photo, a zoom slider up to 3× with zoom out and zoom in, and open, reset and download in the corners.

## Anatomy

Put `ZoomThumb` inside `Zoom`. The steps go anywhere inside `Root`:

```tsx
<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](https://zriz.dev/docs/full-editor.md) 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:

```css
[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×:

```tsx
<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`:

```tsx
"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:

```css
[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`:

```tsx
"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](https://zriz.dev/docs/hooks.md) lists every field `useZoom` returns.

## Keyboard

These keys work while `Zoom` has focus:

| Key | What it does |
| - | - |
| `left` or `right` | Move 1/20 of the track. Right zooms in, left in RTL |
| `up` or `down` | Move 1/20 of the track. Up zooms in |
| `shift` + `arrow keys` | Move half the track |
| `page up` or `page down` | The same as Shift and an arrow key |
| `home` or `end` | 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](https://zriz.dev/docs/composition.md). `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 `maxZoom` on `Root`
- `format` `Intl.NumberFormatOptions`

  How screen readers hear the zoom. By default 2 reads as `"200%"`. An `aria-valuetext` of 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 `maxZoom` on `Root`. Keep it equal to `max` on `Zoom`

### `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
