KQ-1490 · CROP/MARKS
Crop Marks
A selection frame laid out like a print sheet: drag on the picture to draw a frame from the press point, inside it to move it, or on a corner or edge to resize it, with printer's crop marks off each corner, the picture dimmed outside and Shift keeping the aspect. The hand is never pulled — snap lines within reach only light up — and on release the frame settles onto them on springs.snap while the marks, stretched while held, draw back in on the same spring and each caught line flashes with a tick; aspect changes glide on springs.glide. The frame is a focusable group: arrow keys move it, Shift with an arrow resizes it, Alt moves ten steps, Home centres it, and a key that would cross a snap line stops on it first.
cover photo · drag on the photo to draw
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/crop-marks
Props
- value / defaultValue
- { x: number; y: number; width: number; height: number }
- default — / { x: 0.15, y: 0.15, width: 0.7, height: 0.7 }
- The frame as fractions (0 to 1) of the picture, controlled or uncontrolled.
- onValueChange
- (value: CropRect, size: { width: number; height: number }) => void
- default —
- Fires with the settled frame from the release, key or aspect change that set it (not on every move of a drag), and its size in source units.
- aspect
- "free" | "1:1" | "4:3" | "3:2" | "16:9" | number
- default "free"
- A locked width ÷ height; the frame glides to it about its centre. Shift locks the current ratio for one drag.
- snap
- number
- default 10
- The magnet's reach in px: edges released this close to a snap line settle onto it. 0 turns snapping off.
- grid
- "thirds" | "golden" | "quarters" | "none"
- default "thirds"
- The lines it snaps to besides the picture's edges and centre, ticked in the margin.
- step
- number
- default 0.01
- One arrow key's move or resize, as a fraction of the picture; Alt moves ten.
- minSize
- number
- default 24
- The smallest frame a release leaves, in px.
- dim
- number
- default 0.5
- How dark the picture is outside the frame, 0 to 1.
- sourceSize
- { width: number; height: number }
- default the picture's own pixels
- The source's size in pixels, for the W × H readout and the size `onValueChange` reports. Give the picture area the source's aspect so the two agree.
- label
- string
- default "Crop frame"
- The frame's accessible name.
- color
- string
- default —
- The crop marks' colour. By default they are white blended by difference, so they read on any picture in both themes.
- guideColor
- string
- default "var(--accent-bright)"
- The snap guides' colour.
- size
- "sm" | "md" | "lg"
- default "md"
- Mark length (8, 12 or 16px) and the sheet's margin.
- sound
- boolean
- default false
- Tick as the frame settles on a line, on keyboard detents, and faintly as a guide lights mid-drag.
- disabled
- boolean
- default false
- No drawing, moving or keys; the frame leaves the tab order.
- className
- string
- default "h-[220px] w-full"
- Sizes the sheet.
- children
- React.ReactNode
- default —
- The picture, filling the area inside the margin.
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue | { x: number; y: number; width: number; height: number } | — / { x: 0.15, y: 0.15, width: 0.7, height: 0.7 } | The frame as fractions (0 to 1) of the picture, controlled or uncontrolled. |
| onValueChange | (value: CropRect, size: { width: number; height: number }) => void | — | Fires with the settled frame from the release, key or aspect change that set it (not on every move of a drag), and its size in source units. |
| aspect | "free" | "1:1" | "4:3" | "3:2" | "16:9" | number | "free" | A locked width ÷ height; the frame glides to it about its centre. Shift locks the current ratio for one drag. |
| snap | number | 10 | The magnet's reach in px: edges released this close to a snap line settle onto it. 0 turns snapping off. |
| grid | "thirds" | "golden" | "quarters" | "none" | "thirds" | The lines it snaps to besides the picture's edges and centre, ticked in the margin. |
| step | number | 0.01 | One arrow key's move or resize, as a fraction of the picture; Alt moves ten. |
| minSize | number | 24 | The smallest frame a release leaves, in px. |
| dim | number | 0.5 | How dark the picture is outside the frame, 0 to 1. |
| sourceSize | { width: number; height: number } | the picture's own pixels | The source's size in pixels, for the W × H readout and the size `onValueChange` reports. Give the picture area the source's aspect so the two agree. |
| label | string | "Crop frame" | The frame's accessible name. |
| color | string | — | The crop marks' colour. By default they are white blended by difference, so they read on any picture in both themes. |
| guideColor | string | "var(--accent-bright)" | The snap guides' colour. |
| size | "sm" | "md" | "lg" | "md" | Mark length (8, 12 or 16px) and the sheet's margin. |
| sound | boolean | false | Tick as the frame settles on a line, on keyboard detents, and faintly as a guide lights mid-drag. |
| disabled | boolean | false | No drawing, moving or keys; the frame leaves the tab order. |
| className | string | "h-[220px] w-full" | Sizes the sheet. |
| children | React.ReactNode | — | The picture, filling the area inside the margin. |