# Zoom Pan

> A frame for anything bigger than its card, like a bracket: drag to move, ⌘ or Ctrl and scroll to zoom, with −, +, and fit buttons.

- Name: `zoom-pan`
- Status: beta
- Categories: layout
- File: the install writes `zoom-pan.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`, `lucide-react`. The install adds them.
- Other registry items: `@arbokui/button`. The install adds them.
- Gallery: https://arbokui.com/components/zoom-pan
- Registry item: https://arbokui.com/r/zoom-pan.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/zoom-pan
   ```

## Use it for

- Show one big thing that will not fit its card, like a bracket or a floor plan, and let people move around it.
- Give it a height. The content keeps its own size, and the frame starts it at 100%, centered where it fits.
- Drag to move. ⌘ or Ctrl with the wheel zooms around the pointer, and so does a trackpad pinch. Two fingers zoom on a phone.
- A plain scroll still scrolls the page. A hint in the middle says how to zoom until the scrolling stops.
- −, +, and fit sit at the bottom right. Fit shows it all and never zooms past 100% to do it.
- Name it with aria-label, like "Floor plan".

## Avoid it for

- A long list or a wide table. Let the page scroll, or use Table.
- Content people must read closely at every size. Zoomed out, small text is hard to read.
- A frame taller than the screen. Its drags and the page's scroll would fight.

## Props

- `children`: `ReactNode`. What to move and zoom. It keeps its own size.
- `height`: `number`. Default `320`. The frame's height in pixels.
- `min`: `number`. Default `0.25`. The least zoom, as a fraction. 0.25 is 25%.
- `max`: `number`. Default `2`. The most zoom. 2 is 200%.
- `step`: `number`. Default `0.25`. How much − and + change the zoom.
- `aria-label`: `string`. Default `"Zoom and pan"`. Names the frame for screen readers.
- `className`: `string`. Extra classes on the frame. Later classes win, so className='bg-card' replaces the muted fill.

## Accessibility

- The frame is a named group and a Tab stop. Its description says how to use the keys.
- With the frame focused, the arrow keys move, + and − zoom, and 0 fits it all in view.
- Keys pressed on controls inside the content stay with those controls.
- A zoom from a button or a key is said through a polite live message, like "Zoom 150%".
- The −, +, and fit buttons have names. − and + stay focusable at the limit, so focus does not jump away.
- A press that does not move still clicks what is under it. A drag never clicks.
- The zoom hint is hidden from screen readers. The frame's description already says how to zoom.
- With reduced motion, the buttons and keys land the view at once.

## Examples

### A bracket bigger than its card

```tsx
<ZoomPan height={300} aria-label="Vote bracket">
  <div className="flex gap-10 p-4">
    <Card className="w-56 p-4">Dark mode 19 – 17 Shortcuts</Card>
    <Card className="w-56 p-4">Top pick: Dark mode</Card>
  </div>
</ZoomPan>
```

### Zoom in to 400%, in steps of 50%

```tsx
<ZoomPan height={400} max={4} step={0.5} aria-label="Floor plan">
  <img src="/floor-plan.svg" alt="Floor plan" width={1200} height={800} />
</ZoomPan>
```
