Skip to content
Compositions

ProductCard

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

Terracotta - Product image

Usage

import { ProductCard, ProductCardAction, ProductCardActionProps, ProductCardActionTone, ProductCardAddToCart, ProductCardAddToCartAction, ProductCardAddToCartPlacement, ProductCardAddToCartRenderProps, ProductCardAvailability, ProductCardAvailabilityStatus, ProductCardCaption, ProductCardCategory, ProductCardColorCount, ProductCardContent, ProductCardContextValue, ProductCardCost, ProductCardDisplayCategory, ProductCardDisplayOptions, ProductCardDragAdapter, ProductCardDragAdapterProps, ProductCardDragHandle, ProductCardFindSimilar, ProductCardHref, ProductCardImage, ProductCardImageProps, ProductCardImageRenderProps, ProductCardInstallShotToggle, ProductCardLabels, ProductCardMeta, ProductCardMode, ProductCardPrimaryAction, ProductCardPrimaryActionControl, ProductCardProcessing, ProductCardProcessingProps, ProductCardProduct, ProductCardProductCategory, ProductCardProps, ProductCardProvider, ProductCardProviderProps, ProductCardQuickView, ProductCardQuickViewProps, ProductCardRenderAddToCart, ProductCardRenderImage, ProductCardShowFinishesAction, ProductCardSkeleton, ProductCardStatusBadge, ProductCardStatusBadgeProps, ProductCardSwatch, ProductCardSwatchAdd, ProductCardSwatchAddGlyph, ProductCardSwatchAddProps, ProductCardSwatchSelector, ProductCardSwatchSelectorProps, ProductCardTools, ProductCardToolsProps, ProductCardViewInCatalogueAction, ProductCardViewOnlyAction, useProductCard } from "@instruments/materia-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 optimisation.

  • 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 image-first tile for boards and canvases. Brand/name, Quick view and the add control appear on hover or keyboard focus.

labelsProductCardLabels | undefined

Override the card's own strings.

activeboolean | undefined

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

renderImageProductCardRenderImage | undefined

Render slot for framework-specific image components.

draggingboolean | undefined

Swatch mode: set while the host is dragging the tile. Hides Quick view, the caption and the add control and keeps the elevated shell. Distinct from {@link dragAdapter}, which wires a drag handle around the image.

onAdd(() => void) | undefined

Swatch mode: handler for the circular add / suggest control. Omit it for a presentational, inert control that leaves clicks to the tile.

actionProductCardActionProps | undefined

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

addGlyphProductCardSwatchAddGlyph | undefined"spark"

Swatch mode: collapsed glyph of the control.

availabilityProductCardAvailability | undefined"available"

Whether the product can be acted on - see {@link ProductCardAvailability}.

dragAdapterProductCardDragAdapter | undefined

Component slot that wires drag behaviour (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 behaviour (partial prefetch for dynamic routes).

maxSwatchesnumber | undefined5

How many colourway swatches the row shows before the rest go behind +N. Cards under 240px wide show at most four.

menuOpenboolean | undefined

Swatch mode: while true the control reads as Minus and stays visible, so closing the suggestions panel remains reachable. Pair with onAdd.

onCardClick(() => void) | undefined

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

onFindSimilar(() => void) | undefined

Called from the *Find similar* control that replaces the primary action when availability is "out-of-stock", "suspended" or "blocked". Omit it and the primary action renders as it would for any product.

onQuickView(() => void) | undefined

When provided, a "Quick view" 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.

priceReactNode

Price, supplied by the application as already-formatted content. Replaces costIndicator on the meta line. The card does no currency or tax logic. Shipping and delivery flags go through {@link action}, whose label is also a ReactNode.

primaryActionProductCardPrimaryAction | undefined

What this card's one labelled control offers - see {@link ProductCardPrimaryAction}. Omit entirely for a card with no control at all, which is the correct rendering for a product that can do nothing rather than a disabled button. Omitting also preserves the pre-existing cart-only behaviour for callers that pass renderAddToCart and nothing else.

processingProductCardProcessingProps | undefined

Swatch mode: processing footer ("Matching materials" and elapsed time). Replaces the caption and hides Quick view.

product(required)ProductCardProduct
productUrl((product: ProductCardProduct) => string) | undefined

Builds the href of a product's page. Receives the selected variant. Defaults to /products/ followed by the slug.

renderAddToCartProductCardRenderAddToCart | undefined

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

renderToolsProductCardRenderTools | 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)

productNameLines1 | 2 | undefined
showAddToCartboolean | undefined
showProductBrandboolean | undefined
showProductNameboolean | undefined
showSwatchesboolean | undefinedtrue

Show the row of colourway swatches. Single-colour products and single mode never show it.

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 @instruments/materia-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"

Colour role for the label + icon.

ProductCardAddToCart

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

Callback after item is successfully added to cart

ProductCardCaption

PropTypeDefaultDescription
scale"default" | "compact"

Text scale. The compact tile steps both lines down - a genuine divergence between the arms, unlike the link wiring, which is identical and was the reason this component exists.

ProductCardContent

PropTypeDefaultDescription
widthnumber | undefined

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

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 behaviour (partial prefetch for dynamic routes).

onCardClick(() => void) | undefined

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

onQuickView(() => void) | undefined

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

primaryActionProductCardPrimaryAction | undefined

What this card's one labelled control offers - see {@link ProductCardPrimaryAction}. Omit entirely for a card with no control at all, which is the correct rendering for a product that can do nothing rather than a disabled button. Omitting also preserves the pre-existing cart-only behaviour for callers that pass renderAddToCart and nothing else.

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)

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

ProductCardFindSimilar

PropTypeDefaultDescription
onImageboolean | undefined

The control sits over a photograph, so it takes the glass treatment.

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

ProductCardInstallShotToggle

PropTypeDefaultDescription
classNamestring | undefined

ProductCardPrimaryActionControl

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

Callback after a successful add, forwarded to the cart slot.

ProductCardProcessing

PropTypeDefaultDescription
elapsedstring | undefined

Right-aligned elapsed or estimated time (e.g. "23 sec").

labelstring | undefined"Matching materials"

Process chip label.

onClick(() => void) | undefined

Makes the chip a button.

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 behaviour (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: set while the host is dragging the tile. Hides Quick view, the caption and the add control and keeps the elevated shell. Distinct from {@link dragAdapter}, which wires a drag handle around the image.

availabilityProductCardAvailability | undefined"available"

Whether the product can be acted on - see {@link ProductCardAvailability}.

labelsProductCardLabels | undefined

Override the card's own strings.

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 behaviour (partial prefetch for dynamic routes).

maxSwatchesnumber | undefined5

How many colourway swatches the row shows before the rest go behind +N. Cards under 240px wide show at most four.

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 image-first tile for boards and canvases. Brand/name, Quick view and the add control appear on hover or keyboard focus.

onAdd(() => void) | undefined

Swatch mode: handler for the circular add / suggest control. Omit it for a presentational, inert control that leaves clicks to the tile.

addGlyphProductCardSwatchAddGlyph | undefined"spark"

Swatch mode: collapsed glyph of the control.

menuOpenboolean | undefined

Swatch mode: while true the control reads as Minus and stays visible, so closing the suggestions panel remains reachable. Pair with onAdd.

onFindSimilar(() => void) | undefined

Called from the *Find similar* control that replaces the primary action when availability is "out-of-stock", "suspended" or "blocked". Omit it and the primary action renders as it would for any product.

priceReactNode

Price, supplied by the application as already-formatted content. Replaces costIndicator on the meta line. The card does no currency or tax logic. Shipping and delivery flags go through {@link action}, whose label is also a ReactNode.

processingProductCardProcessingProps | undefined

Swatch mode: processing footer ("Matching materials" and elapsed time). Replaces the caption and hides Quick view.

onCardClick(() => void) | undefined

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

onQuickView(() => void) | undefined

When provided, a "Quick view" 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.

primaryActionProductCardPrimaryAction | undefined

What this card's one labelled control offers - see {@link ProductCardPrimaryAction}. Omit entirely for a card with no control at all, which is the correct rendering for a product that can do nothing rather than a disabled button. Omitting also preserves the pre-existing cart-only behaviour for callers that pass renderAddToCart and nothing else.

product(required)ProductCardProduct
productUrl((product: ProductCardProduct) => string) | undefined

Builds the href of a product's page. Receives the selected variant. Defaults to /products/ followed by the slug.

renderAddToCartProductCardRenderAddToCart | undefined

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

renderImageProductCardRenderImage | undefined

Render slot for framework-specific image components.

renderToolsProductCardRenderTools | 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"center"

Where the pill sits along the image's bottom edge. "start" is the swatch tile's bottom-left.

classNamestring
forceVisibleboolean | undefined

Show the pill without hover or focus, for a host that owns the reveal.

labelstring"Quick view"

Pill label.

placement"inline" | "overlay"

Render inside a shared action row rather than as an image overlay.

onQuickView(() => void) | undefined

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

ProductCardSkeleton

PropTypeDefaultDescription
classNamestring
styleCSSProperties

ProductCardStatusBadge

PropTypeDefaultDescription
classNamestring

Context-specific icon styling; text badges keep their owned treatment.

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

Optional icon-only presentation beside the product identity.

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: the control takes the brand tone once it is visible.

classNamestring | undefined
glyphProductCardSwatchAddGlyph | undefined"spark"

Collapsed glyph.

labelsPick<ProductCardLabels, "add" | "closeSuggestions" | "suggest"> | undefined

Strings, overridable; see {@link ProductCardLabels}.

menuOpenboolean | undefined

While true the glyph reads as Minus, the control is brand-toned and stays visible without hover, so the close affordance remains reachable.

onAdd(() => void) | undefined

Click handler. Omit for an inert, presentational control.

ProductCardSwatchSelector

PropTypeDefaultDescription
sizeSwatchSize

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

ProductCardTools

PropTypeDefaultDescription
activeboolean
classNamestring

Also exports

  • interfaceProductCardActionProps
  • typeProductCardActionTone
  • interfaceProductCardAddToCartAction
  • typeProductCardAddToCartPlacement
  • interfaceProductCardAddToCartRenderProps
  • typeProductCardAvailability
  • componentProductCardAvailabilityStatus
  • componentProductCardCategory
  • componentProductCardColorCount
  • interfaceProductCardContextValue
  • componentProductCardCost
  • interfaceProductCardDisplayCategory
  • interfaceProductCardDisplayOptions
  • typeProductCardDragAdapter
  • interfaceProductCardDragAdapterProps
  • interfaceProductCardDragHandle
  • typeProductCardHref
  • interfaceProductCardImageProps
  • interfaceProductCardImageRenderProps
  • interfaceProductCardLabels
  • componentProductCardMeta
  • typeProductCardMode
  • typeProductCardPrimaryAction
  • interfaceProductCardProcessingProps
  • interfaceProductCardProduct
  • interfaceProductCardProductCategory
  • typeProductCardProps
  • interfaceProductCardProviderProps
  • interfaceProductCardQuickViewProps
  • typeProductCardRenderAddToCart
  • typeProductCardRenderImage
  • interfaceProductCardShowFinishesAction
  • interfaceProductCardStatusBadgeProps
  • typeProductCardSwatchAddGlyph
  • interfaceProductCardSwatchAddProps
  • typeProductCardSwatchSelectorProps
  • interfaceProductCardToolsProps
  • interfaceProductCardViewInCatalogueAction
  • interfaceProductCardViewOnlyAction
  • constuseProductCard