Skip to content
← Inventory

ProductCard

Shared ProductCard component. This is a presentational component that accepts handlers via props for app-specific behavior (sidepanels, stores, navigation).

Usage

import { ProductCard, ProductCardAction, ProductCardActionProps, ProductCardActionTone, ProductCardAddToCart, ProductCardAddToCartPlacement, ProductCardAddToCartRenderProps, ProductCardCategory, ProductCardColorCount, ProductCardContent, ProductCardContextValue, ProductCardCost, ProductCardDisplayCategory, ProductCardDisplayOptions, ProductCardDragAdapter, ProductCardDragAdapterProps, ProductCardDragHandle, ProductCardExternalUrl, ProductCardHref, ProductCardImage, ProductCardImageProps, ProductCardImageRenderProps, ProductCardInternalUrl, ProductCardMeta, ProductCardMode, ProductCardProcessing, ProductCardProcessingProps, ProductCardProduct, ProductCardProductCategory, ProductCardProps, ProductCardProvider, ProductCardProviderProps, ProductCardQuickView, ProductCardQuickViewProps, ProductCardRenderAddToCart, ProductCardRenderImage, ProductCardSkeleton, ProductCardSmallSkeleton, ProductCardStatusBadge, ProductCardStatusBadgeProps, ProductCardSwatch, ProductCardSwatchAdd, ProductCardSwatchAddProps, ProductCardSwatchSelector, ProductCardSwatchSelectorProps, ProductCardTools, ProductCardToolsProps, ProductCardUrlPattern, useProductCard } from "@desk/ui/compositions/product-card";

Best practices

  • In Next.js apps pass renderImage wrapping next/image and linkAs={Link} — the defaults are a plain <img> and <a>.

  • Cart UI renders only when a renderAddToCart slot is provided; keep dragAdapter referentially stable (module-level constant).

  • Pass renderImage wrapping next/image in Next.js apps — the default renderer is a plain <img> and skips Next's optimization.

  • Keep dragAdapter referentially stable (a module-level constant) — an inline factory remounts the image subtree and drops the drag ref mid-drag.

Props

ProductCard

PropTypeDefaultDescription
widthnumber | undefined

Fixed width in pixels for image sizing hints. When omitted, card fills its container (fluid width).

modeProductCardMode | undefined

Mode determines the card behavior: - "single": Shows a single product (no carousel, swatches) - "group": Shows a product group with carousel, swatches, etc. - "inspiration": Image-forward card with hover overlay (for explore/masonry feeds) - "swatch": Compact Material Desk tile (Figma Material Card) — image-first idle; hover / keyboard-focus-visible chrome (brand/name + Quickview + spark); selected (not hovered) = shell / selection border only (no spark); menuOpen keeps the Minus close affordance visible; dragging strips chrome and keeps the selection border; optional processing footer

activeboolean | undefined

Whether this card is active (highlighted). Apps should compute this based on their own state (e.g., sidepanel open, recommendations anchor).

draggingboolean | undefined

Swatch-mode canvas drag: when true, hide Quickview / brand-name caption / spark while keeping the selected shell (white border). Distinct from {@link dragAdapter} (catalog dnd-kit wiring).

renderImageProductCardRenderImage | undefined

Render slot for framework-specific image components.

onAdd(() => void) | undefined

Swatch-mode spark button handler. The control mounts in swatch mode and reveals on hover / focus-visible / menuOpen; omit the handler for a presentational (inert, non-focusable) spark that does not capture clicks.

actionProductCardActionProps | undefined

Optional flexible "action notice" shown under the meta row (e.g. ⚡ Ships overnight, ⌕ Find similar). Prop-driven; omit to hide.

dragAdapterProductCardDragAdapter | undefined

Component slot that wires drag behavior (e.g. dnd-kit useDraggable) around the card image. Omit for a static, non-draggable image.

linkAsElementType | undefined

Component to render internal navigation links. Defaults to "a". Pass Link from next/link in Next.js apps to enable client-side navigation and prefetching.

linkPrefetchboolean | undefined

Controls <Link prefetch> on all internal links. true forces full route prefetch (including data) on viewport entry. Default (undefined) uses Next.js auto behavior (partial prefetch for dynamic routes).

maxSwatchesnumber | undefined
menuOpenboolean | undefined

When true, the swatch spark spins to a Minus while an anchored suggestions menu is open. Pair with onAdd as the open/close toggle.

addGlyphProductCardSwatchAddGlyph | undefined

Collapsed glyph for the swatch add control. "spark" (default) reads as "suggest an action" on canvas surfaces; "plus" reads as a direct add-to-board in library search results, where clicking places the material immediately.

onCardClick(() => void) | undefined

Override the default card click behavior. Apps should provide this to handle navigation/sidepanel opening.

onQuickView(() => void) | undefined

When provided, a "Quickview" glass pill appears over the image on hover and invokes this handler (e.g. open a quick-view sidepanel). Prop-driven; omit to hide.

onVariantChange((variantId: string) => void) | undefined

Callback when variant selection changes.

processingProductCardProcessingProps | undefined

Swatch-mode processing footer ("Matching materials" + elapsed). When set, replaces the brand/name caption and hides Quickview.

product(required)ProductCardProduct
productUrlPatternProductCardUrlPattern | undefined"/products/{slug}"

URL pattern for product links. Use {slug} as placeholder.

renderAddToCartProductCardRenderAddToCart | undefined

Render slot for the add-to-cart control, placement-discriminated ("compact" | "cta" | "inspiration"). Omit to render no cart UI at all.

renderTools((props: { actionButtonSize: ButtonProps["size"]; productId: string; variantId: string; }) => ReactNode) | undefined

Render slot for app-specific tools (saves, visual search, etc.) Rendered in the image overlay area.

showActivePointerboolean | undefined

Show the active row pointer under the card when active.

statusBadgeProductCardStatusBadgeProps | undefined

Optional status badge overlaid on the image (e.g. "New"). Prop-driven; omit to hide.

variantIdstring | undefined

For "single" mode: the specific variant ID to display For "group" mode: the initially selected variant ID

overrideImageUrlstring | undefined

Override image URL - used for visual search to show the matched image

priorityboolean | undefined

Prioritise loading images (sets loading="eager" and adds preload link)

showAddToCartboolean | undefined
showProductBrandboolean | undefined
showProductNameboolean | undefined
showSwatchesboolean | undefined
similarityScorenumber | undefined

ProductCardAction

PropTypeDefaultDescription
classNamestring
hrefstring

When set, renders the notice as a link (e.g. "Find similar").

iconimport("../../tokens/icon-component").IconComponent

Optional leading glyph (16px). Pass any icon from @desk/ui/icons.

labelReactNode

The notice text. When omitted, the component renders null.

onClick() => void

When set, renders the notice as a button. Takes precedence over href.

toneProductCardActionTone"muted"

Color role for the label + icon.

ProductCardAddToCart

PropTypeDefaultDescription
classNamestring | undefined
onAdd(() => void) | undefined

Callback after item is successfully added to cart

ProductCardContent

PropTypeDefaultDescription
widthnumber | undefined

Fixed width in pixels for image sizing hints. When omitted, card fills its container (fluid width).

draggingboolean | undefined

Swatch-mode canvas drag: when true, hide Quickview / brand-name caption / spark while keeping the selected shell (white border). Distinct from {@link dragAdapter} (catalog dnd-kit wiring).

actionProductCardActionProps | undefined

Optional flexible "action notice" shown under the meta row (e.g. ⚡ Ships overnight, ⌕ Find similar). Prop-driven; omit to hide.

linkAsElementType | undefined

Component to render internal navigation links. Defaults to "a". Pass Link from next/link in Next.js apps to enable client-side navigation and prefetching.

linkPrefetchboolean | undefined

Controls <Link prefetch> on all internal links. true forces full route prefetch (including data) on viewport entry. Default (undefined) uses Next.js auto behavior (partial prefetch for dynamic routes).

menuOpenboolean | undefined

When true, the swatch spark spins to a Minus while an anchored suggestions menu is open. Pair with onAdd as the open/close toggle.

addGlyphProductCardSwatchAddGlyph | undefined

Collapsed glyph for the swatch add control. "spark" (default) reads as "suggest an action" on canvas surfaces; "plus" reads as a direct add-to-board in library search results, where clicking places the material immediately.

onCardClick(() => void) | undefined

Override the default card click behavior. Apps should provide this to handle navigation/sidepanel opening.

onQuickView(() => void) | undefined

When provided, a "Quickview" glass pill appears over the image on hover and invokes this handler (e.g. open a quick-view sidepanel). Prop-driven; omit to hide.

showActivePointerboolean | undefined

Show the active row pointer under the card when active.

statusBadgeProductCardStatusBadgeProps | undefined

Optional status badge overlaid on the image (e.g. "New"). Prop-driven; omit to hide.

overrideImageUrlstring | undefined

Override image URL - used for visual search to show the matched image

priorityboolean | undefined

Prioritise loading images (sets loading="eager" and adds preload link)

showAddToCartboolean | undefined
showProductBrandboolean | undefined
showProductNameboolean | undefined
similarityScorenumber | undefined
onAddToCartSuccess(() => void) | undefined

ProductCardImage

PropTypeDefaultDescription
overrideImageUrlstring | undefined

Override image URL - used for visual search to show the matched image

priorityboolean | undefined

Prioritise loading this image (sets loading="eager" and adds preload link)

showSimilarityScoreboolean | undefined
similarityScorenumber | undefined

ProductCardProcessing

PropTypeDefaultDescription
elapsedstring | undefined

Right-aligned elapsed / ETA label (e.g. "23 sec").

labelstring | undefined"Matching materials"

Process chip label.

onClick(() => void) | undefined

Optional click on the process chip.

ProductCardProvider

PropTypeDefaultDescription
actionProductCardActionProps | undefined

Optional flexible "action notice" shown under the meta row (e.g. ⚡ Ships overnight, ⌕ Find similar). Prop-driven; omit to hide.

children(required)ReactNode
displayProductCardDisplayOptions | undefined
dragAdapterProductCardDragAdapter | undefined

Component slot that wires drag behavior (e.g. dnd-kit useDraggable) around the card image. Omit for a static, non-draggable image.

activeboolean | undefined

Whether this card is active (highlighted). Apps should compute this based on their own state (e.g., sidepanel open, recommendations anchor).

draggingboolean | undefined

Swatch-mode canvas drag: when true, hide Quickview / brand-name caption / spark while keeping the selected shell (white border). Distinct from {@link dragAdapter} (catalog dnd-kit wiring).

linkAsElementType | undefined

Component to render internal navigation links. Defaults to "a". Pass Link from next/link in Next.js apps to enable client-side navigation and prefetching.

linkPrefetchboolean | undefined

Controls <Link prefetch> on all internal links. true forces full route prefetch (including data) on viewport entry. Default (undefined) uses Next.js auto behavior (partial prefetch for dynamic routes).

maxSwatchesnumber | undefined
modeProductCardMode | undefined

Mode determines the card behavior: - "single": Shows a single product (no carousel, swatches) - "group": Shows a product group with carousel, swatches, etc. - "inspiration": Image-forward card with hover overlay (for explore/masonry feeds) - "swatch": Compact Material Desk tile (Figma Material Card) — image-first idle; hover / keyboard-focus-visible chrome (brand/name + Quickview + spark); selected (not hovered) = shell / selection border only (no spark); menuOpen keeps the Minus close affordance visible; dragging strips chrome and keeps the selection border; optional processing footer

onAdd(() => void) | undefined

Swatch-mode spark button handler. The control mounts in swatch mode and reveals on hover / focus-visible / menuOpen; omit the handler for a presentational (inert, non-focusable) spark that does not capture clicks.

addGlyphProductCardSwatchAddGlyph | undefined

Collapsed glyph for the swatch add control. "spark" (default) reads as "suggest an action" on canvas surfaces; "plus" reads as a direct add-to-board in library search results, where clicking places the material immediately.

menuOpenboolean | undefined

When true, the swatch spark spins to a Minus while an anchored suggestions menu is open. Pair with onAdd as the open/close toggle.

onCardClick(() => void) | undefined

Override the default card click behavior. Apps should provide this to handle navigation/sidepanel opening.

onQuickView(() => void) | undefined

When provided, a "Quickview" glass pill appears over the image on hover and invokes this handler (e.g. open a quick-view sidepanel). Prop-driven; omit to hide.

processingProductCardProcessingProps | undefined

Swatch-mode processing footer ("Matching materials" + elapsed). When set, replaces the brand/name caption and hides Quickview.

onVariantChange((variantId: string) => void) | undefined

Callback when variant selection changes.

product(required)ProductCardProduct
productUrlPatternProductCardUrlPattern | undefined"/products/{slug}"

URL pattern for product links. Use {slug} as placeholder.

renderAddToCartProductCardRenderAddToCart | undefined

Render slot for the add-to-cart control, placement-discriminated ("compact" | "cta" | "inspiration"). Omit to render no cart UI at all.

renderImageProductCardRenderImage | undefined

Render slot for framework-specific image components.

renderTools((props: { actionButtonSize: ButtonProps["size"]; productId: string; variantId: string; }) => ReactNode) | undefined

Render slot for app-specific tools (saves, visual search, etc.) Rendered in the image overlay area.

showActivePointerboolean | undefined

Show the active row pointer under the card when active.

statusBadgeProductCardStatusBadgeProps | undefined

Optional status badge overlaid on the image (e.g. "New"). Prop-driven; omit to hide.

variantIdstring | undefined

For "single" mode: the specific variant ID to display For "group" mode: the initially selected variant ID

widthnumber | undefined

Fixed width in pixels for image sizing hints. When omitted, card fills its container (fluid width).

ProductCardQuickView

PropTypeDefaultDescription
align"center" | "start" | undefined

Horizontal placement over the image. - "center" — catalog full card (default) - "start" — swatch Material Card (Figma bottom-left)

classNamestring
forceVisibleboolean | undefined

Keep the pill visible without hover (e.g. Storybook forced state). Swatch selection does not use this — Quickview stays hover / keyboard-focus-visible gated (not mouse :focus-within after click).

labelstring"Quickview"

Pill label.

onQuickView(() => void) | undefined

Invoked when the pill is activated. When omitted, nothing renders.

ProductCardSkeleton

PropTypeDefaultDescription
classNamestring

ProductCardSmallSkeleton

PropTypeDefaultDescription
classNamestring
styleCSSProperties

ProductCardStatusBadge

PropTypeDefaultDescription
labelstring

Badge text. When omitted, nothing renders.

tone"neutral" | "brand" | "danger" | "warning" | "success" | "info""brand" (the yellow "New" badge).

Badge tone.

ProductCardSwatch

PropTypeDefaultDescription
overrideImageUrlstring | undefined
priorityboolean | undefined

ProductCardSwatchAdd

PropTypeDefaultDescription
activeboolean | undefined

Selected / active → brand yellow when the control is revealed; does not force visibility on its own.

classNamestring | undefined
wrapperClassNamestring | undefined

Overrides the outer anchor (Center) classes. Default pins the control to the card's top-right (absolute top-1 right-1). Section frames pass a relative anchor so the same control can sit in screen-space chrome.

menuOpenboolean | undefined

When true, the glyph spins from Sparkle to Minus, brand chrome stays on, and the control stays visible even without hover — so the close affordance remains reachable while the suggestions panel is open.

revealedboolean | undefined

Parent pointer-hover has already revealed chrome. Kept separate from active so click-to-select under an open hover does not retarget the reveal classes (same end visual; selection only flips tone).

onAdd(() => void) | undefined

Click handler for the spark. When omitted, the control stays visible but is inert (no pointer/keyboard capture) so parent surfaces can own selection.

glyphProductCardSwatchAddGlyph | undefined

Collapsed glyph. "spark" (default) — suggest an action; "plus" — direct add-to-board (library search results). The menuOpen Minus morph behaves identically for both.

ProductCardSwatchSelector

PropTypeDefaultDescription
sizeSwatchSize

Swatch size, inherited by items via context unless they override it.

ProductCardTools

PropTypeDefaultDescription
activeboolean
classNamestring

Also exports

  • interfaceProductCardActionProps
  • typeProductCardActionTone
  • typeProductCardAddToCartPlacement
  • interfaceProductCardAddToCartRenderProps
  • componentProductCardCategory
  • componentProductCardColorCount
  • interfaceProductCardContextValue
  • componentProductCardCost
  • interfaceProductCardDisplayCategory
  • interfaceProductCardDisplayOptions
  • typeProductCardDragAdapter
  • interfaceProductCardDragAdapterProps
  • interfaceProductCardDragHandle
  • typeProductCardExternalUrl
  • typeProductCardHref
  • interfaceProductCardImageProps
  • interfaceProductCardImageRenderProps
  • typeProductCardInternalUrl
  • componentProductCardMeta
  • typeProductCardMode
  • interfaceProductCardProcessingProps
  • interfaceProductCardProduct
  • interfaceProductCardProductCategory
  • typeProductCardProps
  • interfaceProductCardProviderProps
  • interfaceProductCardQuickViewProps
  • typeProductCardRenderAddToCart
  • typeProductCardRenderImage
  • interfaceProductCardStatusBadgeProps
  • interfaceProductCardSwatchAddProps
  • typeProductCardSwatchSelectorProps
  • interfaceProductCardToolsProps
  • typeProductCardUrlPattern
  • constuseProductCard