ProductCard
Shared ProductCard component. This is a presentational component that accepts handlers via props for app-specific behaviour (sidepanels, stores, navigation).
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
renderImagewrappingnext/imageandlinkAs={Link}- the defaults are a plain<img>and<a>.Cart UI renders only when a
renderAddToCartslot is provided; keepdragAdapterreferentially stable (module-level constant).Pass
renderImagewrappingnext/imagein Next.js apps - the default renderer is a plain<img>and skips Next's optimisation.Keep
dragAdapterreferentially stable (a module-level constant) - an inline factory remounts the image subtree and drops the drag ref mid-drag.
Props
ProductCard
| Prop | Type | Default | Description |
|---|---|---|---|
| width | number | undefined | - | Fixed width in pixels for image sizing hints. When omitted, card fills its container (fluid width). |
| mode | ProductCardMode | 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. |
| labels | ProductCardLabels | undefined | - | Override the card's own strings. |
| active | boolean | undefined | - | Whether this card is active (highlighted). Apps should compute this based on their own state (e.g., sidepanel open, recommendations anchor). |
| renderImage | ProductCardRenderImage | undefined | - | Render slot for framework-specific image components. |
| dragging | boolean | 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. |
| action | ProductCardActionProps | undefined | - | Optional flexible "action notice" shown under the meta row (e.g. ⚡ Ships overnight, ⌕ Find similar). Prop-driven; omit to hide. |
| addGlyph | ProductCardSwatchAddGlyph | undefined | "spark" | Swatch mode: collapsed glyph of the control. |
| availability | ProductCardAvailability | undefined | "available" | Whether the product can be acted on - see {@link ProductCardAvailability}. |
| dragAdapter | ProductCardDragAdapter | undefined | - | Component slot that wires drag behaviour (e.g. dnd-kit |
| linkAs | ElementType | undefined | - | Component to render internal navigation links.
Defaults to |
| linkPrefetch | boolean | undefined | - | Controls |
| maxSwatches | number | undefined | 5 | How many colourway swatches the row shows before the rest go behind |
| menuOpen | boolean | undefined | - | Swatch mode: while true the control reads as Minus and stays visible, so
closing the suggestions panel remains reachable. Pair with |
| 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 |
| 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. |
| price | ReactNode | - | Price, supplied by the application as already-formatted content. Replaces
|
| primaryAction | ProductCardPrimaryAction | 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 |
| processing | ProductCardProcessingProps | 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 |
| renderAddToCart | ProductCardRenderAddToCart | undefined | - | Render slot for the add-to-cart control, placement-discriminated ("cta" | "inspiration"). Omit to render no cart UI at all. |
| renderTools | ProductCardRenderTools | undefined | - | Render slot for app-specific tools (saves, visual search, etc.) Rendered in the image overlay area. |
| showActivePointer | boolean | undefined | - | Show the active row pointer under the card when active. |
| statusBadge | ProductCardStatusBadgeProps | undefined | - | Optional status badge overlaid on the image (e.g. "New"). Prop-driven; omit to hide. |
| variantId | string | undefined | - | For "single" mode: the specific variant ID to display For "group" mode: the initially selected variant ID |
| overrideImageUrl | string | undefined | - | Override image URL - used for visual search to show the matched image |
| priority | boolean | undefined | - | Prioritise loading images (sets loading="eager" and adds preload link) |
| productNameLines | 1 | 2 | undefined | - | |
| showAddToCart | boolean | undefined | - | |
| showProductBrand | boolean | undefined | - | |
| showProductName | boolean | undefined | - | |
| showSwatches | boolean | undefined | true | Show the row of colourway swatches. Single-colour products and |
| similarityScore | number | undefined | - |
ProductCardAction
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | |
| href | string | - | When set, renders the notice as a link (e.g. "Find similar"). |
| icon | import("../../tokens/icon-component").IconComponent | - | Optional leading glyph (16px). Pass any icon from |
| label | ReactNode | - | The notice text. When omitted, the component renders |
| onClick | () => void | - | When set, renders the notice as a button. Takes precedence over |
| tone | ProductCardActionTone | "muted" | Colour role for the label + icon. |
ProductCardAddToCart
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | - | |
| onAdd | (() => void) | undefined | - | Callback after item is successfully added to cart |
ProductCardCaption
| Prop | Type | Default | Description |
|---|---|---|---|
| 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
| Prop | Type | Default | Description |
|---|---|---|---|
| width | number | undefined | - | Fixed width in pixels for image sizing hints. When omitted, card fills its container (fluid width). |
| action | ProductCardActionProps | undefined | - | Optional flexible "action notice" shown under the meta row (e.g. ⚡ Ships overnight, ⌕ Find similar). Prop-driven; omit to hide. |
| linkAs | ElementType | undefined | - | Component to render internal navigation links.
Defaults to |
| linkPrefetch | boolean | undefined | - | Controls |
| 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. |
| primaryAction | ProductCardPrimaryAction | 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 |
| showActivePointer | boolean | undefined | - | Show the active row pointer under the card when active. |
| statusBadge | ProductCardStatusBadgeProps | undefined | - | Optional status badge overlaid on the image (e.g. "New"). Prop-driven; omit to hide. |
| overrideImageUrl | string | undefined | - | Override image URL - used for visual search to show the matched image |
| priority | boolean | undefined | - | Prioritise loading images (sets loading="eager" and adds preload link) |
| productNameLines | 1 | 2 | undefined | - | |
| showAddToCart | boolean | undefined | - | |
| showProductBrand | boolean | undefined | - | |
| showProductName | boolean | undefined | - | |
| similarityScore | number | undefined | - | |
| onAddToCartSuccess | (() => void) | undefined | - |
ProductCardFindSimilar
| Prop | Type | Default | Description |
|---|---|---|---|
| onImage | boolean | undefined | - | The control sits over a photograph, so it takes the glass treatment. |
ProductCardImage
| Prop | Type | Default | Description |
|---|---|---|---|
| overrideImageUrl | string | undefined | - | Override image URL - used for visual search to show the matched image |
| priority | boolean | undefined | - | Prioritise loading this image (sets loading="eager" and adds preload link) |
| showSimilarityScore | boolean | undefined | - | |
| similarityScore | number | undefined | - |
ProductCardInstallShotToggle
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | - |
ProductCardPrimaryActionControl
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | - | |
| onAdded | (() => void) | undefined | - | Callback after a successful add, forwarded to the cart slot. |
ProductCardProcessing
| Prop | Type | Default | Description |
|---|---|---|---|
| elapsed | string | undefined | - | Right-aligned elapsed or estimated time (e.g. |
| label | string | undefined | "Matching materials" | Process chip label. |
| onClick | (() => void) | undefined | - | Makes the chip a button. |
ProductCardProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| action | ProductCardActionProps | undefined | - | Optional flexible "action notice" shown under the meta row (e.g. ⚡ Ships overnight, ⌕ Find similar). Prop-driven; omit to hide. |
| children*(required) | ReactNode | - | |
| display | ProductCardDisplayOptions | undefined | - | |
| dragAdapter | ProductCardDragAdapter | undefined | - | Component slot that wires drag behaviour (e.g. dnd-kit |
| active | boolean | undefined | - | Whether this card is active (highlighted). Apps should compute this based on their own state (e.g., sidepanel open, recommendations anchor). |
| dragging | boolean | 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. |
| availability | ProductCardAvailability | undefined | "available" | Whether the product can be acted on - see {@link ProductCardAvailability}. |
| labels | ProductCardLabels | undefined | - | Override the card's own strings. |
| linkAs | ElementType | undefined | - | Component to render internal navigation links.
Defaults to |
| linkPrefetch | boolean | undefined | - | Controls |
| maxSwatches | number | undefined | 5 | How many colourway swatches the row shows before the rest go behind |
| mode | ProductCardMode | 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. |
| addGlyph | ProductCardSwatchAddGlyph | undefined | "spark" | Swatch mode: collapsed glyph of the control. |
| menuOpen | boolean | undefined | - | Swatch mode: while true the control reads as Minus and stays visible, so
closing the suggestions panel remains reachable. Pair with |
| onFindSimilar | (() => void) | undefined | - | Called from the *Find similar* control that replaces the primary action
when |
| price | ReactNode | - | Price, supplied by the application as already-formatted content. Replaces
|
| processing | ProductCardProcessingProps | 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. |
| primaryAction | ProductCardPrimaryAction | 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 |
| product*(required) | ProductCardProduct | - | |
| productUrl | ((product: ProductCardProduct) => string) | undefined | - | Builds the href of a product's page. Receives the selected variant.
Defaults to |
| renderAddToCart | ProductCardRenderAddToCart | undefined | - | Render slot for the add-to-cart control, placement-discriminated ("cta" | "inspiration"). Omit to render no cart UI at all. |
| renderImage | ProductCardRenderImage | undefined | - | Render slot for framework-specific image components. |
| renderTools | ProductCardRenderTools | undefined | - | Render slot for app-specific tools (saves, visual search, etc.) Rendered in the image overlay area. |
| showActivePointer | boolean | undefined | - | Show the active row pointer under the card when active. |
| statusBadge | ProductCardStatusBadgeProps | undefined | - | Optional status badge overlaid on the image (e.g. "New"). Prop-driven; omit to hide. |
| variantId | string | undefined | - | For "single" mode: the specific variant ID to display For "group" mode: the initially selected variant ID |
| width | number | undefined | - | Fixed width in pixels for image sizing hints. When omitted, card fills its container (fluid width). |
ProductCardQuickView
| Prop | Type | Default | Description |
|---|---|---|---|
| align | "center" | "start" | undefined | "center" | Where the pill sits along the image's bottom edge. |
| className | string | - | |
| forceVisible | boolean | undefined | - | Show the pill without hover or focus, for a host that owns the reveal. |
| label | string | "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
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | |
| style | CSSProperties | - |
ProductCardStatusBadge
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Context-specific icon styling; text badges keep their owned treatment. |
| icon | import("../../tokens/icon-component").IconComponent | - | Optional icon-only presentation beside the product identity. |
| label | string | - | Badge text. When omitted, nothing renders. |
| tone | "neutral" | "brand" | "danger" | "warning" | "success" | "info" | "brand" (the yellow "New" badge). | Badge tone. |
ProductCardSwatch
| Prop | Type | Default | Description |
|---|---|---|---|
| overrideImageUrl | string | undefined | - | |
| priority | boolean | undefined | - |
ProductCardSwatchAdd
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | undefined | - | Selected: the control takes the brand tone once it is visible. |
| className | string | undefined | - | |
| glyph | ProductCardSwatchAddGlyph | undefined | "spark" | Collapsed glyph. |
| labels | Pick<ProductCardLabels, "add" | "closeSuggestions" | "suggest"> | undefined | - | Strings, overridable; see {@link ProductCardLabels}. |
| menuOpen | boolean | 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
| Prop | Type | Default | Description |
|---|---|---|---|
| size | SwatchSize | - | Swatch size, inherited by items via context unless they override it. |
ProductCardTools
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | - | |
| className | string | - |
Also exports
- interface
ProductCardActionProps - type
ProductCardActionTone - interface
ProductCardAddToCartAction - type
ProductCardAddToCartPlacement - interface
ProductCardAddToCartRenderProps - type
ProductCardAvailability - component
ProductCardAvailabilityStatus - component
ProductCardCategory - component
ProductCardColorCount - interface
ProductCardContextValue - component
ProductCardCost - interface
ProductCardDisplayCategory - interface
ProductCardDisplayOptions - type
ProductCardDragAdapter - interface
ProductCardDragAdapterProps - interface
ProductCardDragHandle - type
ProductCardHref - interface
ProductCardImageProps - interface
ProductCardImageRenderProps - interface
ProductCardLabels - component
ProductCardMeta - type
ProductCardMode - type
ProductCardPrimaryAction - interface
ProductCardProcessingProps - interface
ProductCardProduct - interface
ProductCardProductCategory - type
ProductCardProps - interface
ProductCardProviderProps - interface
ProductCardQuickViewProps - type
ProductCardRenderAddToCart - type
ProductCardRenderImage - interface
ProductCardShowFinishesAction - interface
ProductCardStatusBadgeProps - type
ProductCardSwatchAddGlyph - interface
ProductCardSwatchAddProps - type
ProductCardSwatchSelectorProps - interface
ProductCardToolsProps - interface
ProductCardViewInCatalogueAction - interface
ProductCardViewOnlyAction - const
useProductCard
Composition
Builds on
Badge, Box, Button, Center, Icon, MarqueeText, MediaFrame, Popover, Skeleton, Stack, SwatchSelector, Text, Tooltip
Used by