KQ-1331 · PRODUCT/DETAIL

Product Detail

The buying half of a product page, where every choice shows on the piece: the pictures sit in a strip that follows the finger 1:1, rubber-bands at the ends and commits by projection on glide, and a loupe follows the pointer 1:1 to show the glaze at lens power. Choosing a colour floods the new glaze across the picture from the edge nearest the picker on drift, a size grows or settles the piece from its foot on glide, the price rolls its digits on snap, and Add to bag becomes the stepper in the same box as the fill contracts onto the count on glide while − and + scale in on snap. Colours and sizes are radio groups moved with the arrow keys, the pictures a tablist whose panel takes Enter to open the loupe and arrows to move it, and a buy bar docks at the bottom of the scroll box whenever the buy row is out of it.

KQ-1331 · PRODUCT/DETAIL
Left and Right change the picture. Enter opens the loupe; arrows move it, Shift moves it further, Escape puts it away.
Thrown by hand

Fernworks Studio Pottery

Field Mug

Rated 4.8 out of 5,· 212 reviews

€32.00VAT included

Iron-speckled stoneware with a glaze that pools at the foot. Every mug is thrown, trimmed and dipped by hand, so no two speckle alike.

ColourEmber

Size12 oz · 350 ml

ember · 12 oz · look closer, then add it

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/product-detail

Props

product
ProductInfo
default defaultProduct
The product: brand, name, price, rating, colours, sizes, pictures (render functions of a colour and size), unavailable combinations and detail sections.
value / defaultValue
ProductSelection
default — / the first colour with the featured size
Controlled or initial colour and size, as { colour, size } ids.
onValueChange
(selection: ProductSelection) => void
default —
Fires from the swatch, tile or size the visitor chose, with the new selection.
quantity / defaultQuantity
number
default — / 0
The count in the bag for the selected variant; 0 shows Add to bag.
onQuantityChange
(quantity: number, selection: ProductSelection) => void
default —
Fires from Add to bag and the stepper with the new count and the variant it is for.
onAdd
(selection: ProductSelection) => void | Promise<void>
default —
Add to bag was pressed. A promise holds the button pending; a rejection's message is shown under it.
onNotify
(selection: ProductSelection) => void
default —
Notify me was pressed on a sold-out variant.
view / defaultView
number
default — / 0
Controlled or initial picture index.
onViewChange
(index: number) => void
default —
Fires from the drag, thumbnail or key that changed the picture.
estimate
(postcode: string, now: number) => DeliveryEstimate | null | Promise<DeliveryEstimate | null>
default defaultEstimate
Answers a postcode with a delivery estimate; null means no delivery there.
defaultPostcode
string
default ""
A postcode already known; its estimate shows from the start.
onEstimate
(postcode: string, estimate: DeliveryEstimate | null) => void
default —
Fires with each checked postcode and its answer.
now
Date | number
default 2 Oct 2026, 09:30 UTC
The moment estimates count from; never read from the clock during render.
maxQuantity
number
default 10
The most one variant can have in the bag; never more than its stock.
lens
number
default 2.5
The loupe's power, 1.5 to 4.
variants
"swatches" | "tiles" | "chips"
default "swatches"
How colours are offered: glaze dots, little pictures of the piece in each colour, or named pills (sizes become pills too).
sticky
boolean
default true
Dock a buy bar at the bottom of the scroll box while the buy row is out of it, and pin the pictures beside the details.
format
(amount: number) => string
default euros
Formats an amount for display.
addLabel
string
default "Add to bag"
The buy button's words.
status
"ready" | "loading" | "error"
default "ready"
Loading draws placeholders; error offers Retry.
onRetry
() => void
default —
The Retry button of the error state.
label
string
default —
The region's accessible name when it should differ from the product's name.
sound
boolean
default false
A plip on each pick and when the loupe opens, a pop on Add to bag and the stepper.
disabled
boolean
default false
Shows the product but takes no input.
className
string
default —
Classes for the region; give it a max height and it scrolls inside itself, which is what the docked bar answers to.