Skip to content
Sign in
Image studioPreference pairMenu

Preference pairPro

Two images side by side to train a personal style. 1 or 2 picks the better one, T marks a tie, S skips and Z takes it back, with progress toward a style profile.

ProRate pairs to train a personal style: 1 or 2 picks, T ties, S skips, Z undoes. Four more ratings unlock the profile.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/preference-pair

Usage

tsx
import { PreferencePair } from "@/components/ui/chatcn/pro/preference-pair"

<PreferencePair
  pairs={pairs}
  rated={profile.ratings}
  goal={40}
  onChoose={(choice, pair) => api.rate(pair.id, choice)}
  onUndo={(choice, pair) => api.unrate(pair.id)}
  onLoadMore={fetchMorePairs}
/>

pairs is a queue: append to it and rating carries on. Ratings count toward goal (skips do not); when the goal is reached the header turns to Profile ready and the milestone is announced. The next two pairs are preloaded so a pick swaps to decoded images.

When the queue runs out, loading shows a placeholder pair; otherwise the finished state says how many ratings are left and offers onLoadMore, or onProfileOpen once the profile is ready.

Examples

States

Loading the next pair, an image that failed to load, a finished queue, a ready profile, nothing to rate, disabled and right to left.

ProLoading the next pair, a failed image, a finished queue, a ready profile, nothing to rate and disabled.

Props

PropTypeNotes
pairsPreferencePairItem[]Required. id, a, b and an optional prompt
onChoose(choice: "a" | "b" | "tie" | "skip", pair) => voidEvery choice, skips included
onUndo(choice, pair) => voidThe last choice was taken back
ratednumberRatings made before this session. Default 0
goalnumberRatings a profile needs. Default 40
loadingbooleanMore pairs are on the way
onLoadMore, onProfileOpen() => voidActions in the finished state
aspectnumberWidth over height of both frames. Default 4 / 5
questionstringDefault "Which image do you prefer?"
hotkeys"scoped" | "global" | "off"Default scoped to [data-hotkey-scope]
disabledboolean

Keyboard

KeyAction
1, 2Prefer the first or the second image (start and end, so right to left keeps 1 first)
TA tie: both are as good
SSkip the pair without rating it
Z or BackspaceTake back the last choice

Accessibility

  • Each image is a button named from its visible number, "1, prefer this image", and described by its alt text.
  • The training progress is a progressbar with the count in words. Every choice and the milestone are announced politely.
  • Shortcuts never fire while typing in a field. An image that fails to load says so and offers a retry; it cannot be picked.

Motion

  • The pick never animates: its ring and mark show at once and hold for 180 ms so you see what was recorded.
  • Picked with a pointer, the next pair fades in over 200 ms; from the keyboard it swaps instantly, because rating is done hundreds of times.
  • The progress fill eases over 300 ms and jumps under reduced motion.