KQ-1733 · PIN/CRITIQUE

Pin Critique

A design-critique board: numbered pins on an artwork, each starting a thread in the column beside it. Clicking the artwork or pressing P drops a pin at the cursor — it falls 16px and lands on springs.recoil with two small bounces while its ground shadow grows and its numeral rolls up into the head on springs.snap just behind the touch-down — and the composer opens for what you see. Hovering or focusing a thread draws a ring around its pin on springs.glide while the pin lifts on snap; pins drag 1:1 and settle on snap with the throw, resolved pins fade to a check, and below a 40rem container the threads open as a sheet that rises on glide, follows a downward drag and pans the artwork so the pin stays in view. Pins are one roving tab stop: arrows move between them, Alt with an arrow nudges one, Shift with an arrow moves the drop cursor and Escape closes what is open.

KQ-1733 · PIN/CRITIQUE

Booking screen · v4

4 open, 1 resolved

P drops a pin at the cursor. Shift with an arrow moves the cursor. Arrow keys move between pins, Alt with an arrow moves the focused pin, Enter opens its thread.

5 pins · 4 open · 1 resolved

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/pin-critique

Props

pins / defaultPins
CritiquePin[]
default — / defaultCritiquePins
Pins as { id, x, y }, positions as 0–1 fractions of the artwork; a pin's number is its place in the list.
onPinsChange
(pins) => void
default —
Fires with every pin after a drop is posted or a pin is dragged or nudged.
threads / defaultThreads
CritiqueThread[]
default — / defaultCritiqueThreads
Threads as { id, pinId, resolved?, comments: { id, author, at, text }[] }.
onThreadsChange
(threads) => void
default —
Fires with every thread after a post, a reply or a resolve.
selected / defaultSelected / onSelectedChange
string | null
default — / null
The open thread's id, controlled or initial, and the callback from the pin, card or key that changed it.
onPin
(pin, thread) => void
default —
A dropped pin was posted with its first comment; both are already in the lists.
onReply
(threadId, comment) => void
default —
A reply was posted on a thread.
onResolve
(threadId, resolved) => void
default —
A thread was resolved (true) or reopened (false).
artwork
ReactNode
default a drawn Waylight booking screen
What is being critiqued: an image or drawn design that fills its box.
artworkLabel
string
default "Waylight booking screen, version 4"
The artwork's accessible name; it also names the canvas group.
aspect
number
default 0.75
The artwork's width over its height; the canvas fits it inside the box.
pinStyle
"drop" | "disc" | "tab"
default "drop"
A teardrop whose point is the spot, a disc centred on it, or a speech tab whose corner is it (flipped near the right edge).
density
"compact" | "regular" | "roomy"
default "regular"
Pin size (22/26/30px), thread type and card padding.
accent
string
default "var(--accent-bright)"
Pins, rings, the open card's edge and the post buttons — any CSS colour; pins take its hue at a fixed lightness so white numerals read in both themes.
showResolved / defaultShowResolved / onShowResolvedChange
boolean
default — / true
Whether resolved pins and threads show, controlled or initial, and the Resolved toggle's callback.
author
string
default "Noor Haddad"
The name new comments are posted under.
now
number | Date
default defaultCritiqueNow
The moment relative times count from, so server and browser render the same text.
title
string
default "Booking screen · v4"
The board's heading and accessible name.
status
"ready" | "loading" | "error"
default "ready"
Loading shows a skeleton artwork and threads; error shows Try again.
onRetry
() => void
default —
Try again was pressed after the board failed to load.
height
number
default 540
The board's height in px; the canvas fits inside it and the threads scroll.
sound
boolean
default false
Play the pop of a pin landing (and a softer one on resolve and on a dragged pin's landing).
disabled
boolean
default false
Read only: nothing can be dropped, moved, posted or resolved.
className
string
default —
Classes for the board.