ProductCard
A product in a grid or a carousel, linked to href: image, ribbon, name, price and
availability, with what layout adds for a consumer or a business buyer, and slots
for what a site adds — ribbon, compare, swatches, packSize, actions,
availability, hoverImage.
The price row sits at the foot, so prices line up across a row of cards.
Server-renderable; routing and the image component are the app's. Inside a
ProductListingLayout set to list it lays out as a row.
import { ProductCard } from "@evolve-storefront/feature-product/product-card";Renders inside ProductListingProvider.
Preview
Usage
Image, ribbon, name, price and availability; both links lead to the product.
<ProductCard product={variant} href={productHref(variant.product.slug)} onLinkClick={() => emit("productClicked", { product, list, position })} renderLink={({ href, children, ...props }) => <Link href={href} {...props}>{children}</Link>} renderImage={(props) => <Image {...props} width={352} height={235} />} actions={<ShoppingListButton sku={variant.sku} />}/>;Props
| Prop | Type | Description |
|---|---|---|
product | ProductCardData | |
href | string | The product page — the app's link builder. |
onLinkClick? | MouseEventHandler<HTMLAnchorElement> | Both links report a click through this — |
renderLink? | ((props: ProductCardLinkProps) => ReactNode) | |
renderImage? | ((props: ProductCardImageRenderProps) => ReactNode) | |
hoverImage? | { url: string; } | null | Fades in over the image on hover: |
loading? | "lazy" | "eager" | |
headingLevel? | HeadingLevel | |
ribbon? | ReactNode | Replaces the product's own ribbon. |
compare? | ReactNode | A compare toggle, above the name — not over the image, whose link would crowd a control too small to tap apart from it. |
swatches? | ReactNode | Above the brand: the colors it comes in, |
actions? | ReactNode | Beside the price, at the foot of the card: a stepper with add to cart, a shopping-list button. |
packSize? | ReactNode | Under the name: how the product is packed, such as "12 pads × 100 sheets". |
children? | ReactNode | Under the card's details. |
layout? | ProductCardLayout |
|
vatDisplay? | VatDisplay | The store's VAT display; by default gross for a consumer and net for a business. |
availability? | ReactNode | Replaces the availability line, such as with a delivery promise. |
pricePlaceholder? | ReactNode | Shown instead of the price, such as a sign-in link where the store hides prices from guests. |
Any other props go to the root element, like on a plain HTML element.
Change the layout
Pass children to ProductCard to arrange its parts yourself. Every part is exported from @evolve-storefront/feature-product/product-card.
ProductCardAvailabilityWhether the product can be had, as a status tag; data-availability carries the state.
| Prop | Type | Description |
|---|---|---|
availability | ProductAvailability | null | undefined |
ProductCardBrandA consumer card's brand, above the name.
| Prop | Type | Description |
|---|---|---|
brand | string | null | undefined |
ProductCardColorsHow many colors a product comes in, with a swatch per color, for a card's swatches.
| Prop | Type | Description |
|---|---|---|
colors | readonly ProductCardColor[] | From |
max? | number | How many swatches to show before the rest is summed up as "+3". |
Any other props go to the root element, like on a plain HTML element.
ProductCardDiscountA discounted price's original amount struck through, the saving in percent, and, given lowest, the lowest price in the 30 days before the discount (EU Omnibus).
| Prop | Type | Description |
|---|---|---|
money | Money | null | undefined | |
regular | Money | null | undefined | |
lowest? | Money | null |
ProductCardImageThe card's image, linked to the product but out of the tab order — the name is the one stop a keyboard makes — with a placeholder of the same shape when there is none.
| Prop | Type | Description |
|---|---|---|
image | { url: string; alt?: string | null | undefined; } | null | undefined | |
name | string | Used when the image has no alt text of its own. |
href | string | |
loading? | "lazy" | "eager" | |
onLinkClick? | MouseEventHandler<HTMLAnchorElement> | |
renderLink? | ((props: ProductCardLinkProps) => ReactNode) | |
renderImage? | ((props: ProductCardImageRenderProps) => ReactNode) | |
hoverImage? | { url: string; } | null | Shown over the image while the pointer is on it, such as |
children? | ReactNode | Over the image: the ribbon, a compare toggle. |
Any other props go to the root element, like on a plain HTML element.
ProductCardMetaA business card's line above the name: the brand and the article number.
| Prop | Type | Description |
|---|---|---|
brand | string | null | undefined | |
sku | string | null | undefined |
ProductCardNameThe card's name as a heading, linked to the product.
| Prop | Type | Description |
|---|---|---|
name | string | |
href | string | |
headingLevel? | HeadingLevel | |
onLinkClick? | MouseEventHandler<HTMLAnchorElement> | |
renderLink? | ((props: ProductCardLinkProps) => ReactNode) |
ProductCardPriceThe card's price, in its own Price parts.
| Prop | Type | Description |
|---|---|---|
money | Money | null | undefined | |
locale | string |
ProductCardRatingThe average rating and how many reviews it comes from.
| Prop | Type | Description |
|---|---|---|
reviews | ListingReviews | null | undefined |
ProductCardRibbonThe product's ribbon — "New", "-20%" — its kind as data-kind for a brand to style.
| Prop | Type | Description |
|---|---|---|
ribbon | { kind?: string | null | undefined; label?: string | null | undefined; } | null | undefined |
Any other props go to the root element, like on a plain HTML element.
ProductCardSkuA business card's article number.
| Prop | Type | Description |
|---|---|---|
sku | string | null | undefined |
ProductCardStockHow much a business buyer can have, as a status tag toned by level; data-stock carries it.
| Prop | Type | Description |
|---|---|---|
stock | Pick<ProductStock, "level" | "lowStockQuantity"> | null | undefined |
ProductCardTierHintThe first quantity that lowers the unit price, as "From €8 at 6 pieces".
| Prop | Type | Description |
|---|---|---|
tiers | ListingPriceTier[] | null | undefined | |
base | Money | null | undefined | |
vatDisplay | VatDisplay |
Examples
Vat DisplayThe same card in a store that shows prices including VAT, and in one that shows them excluding VAT, as most B2B stores do: gross first, then net.
<ProductCard product={variant} href={href} vatDisplay="gross" />;<ProductCard product={variant} href={href} vatDisplay="net" />;ConsumerA consumer's card: the colors it comes in, the brand above the name, the rating, and a discounted price with the original struck through and the saving, including VAT.
<ProductCard product={variant} href={href} layout="consumer" swatches={<ProductCardColors colors={productCardColors(variant)} />} hoverImage={productCardHoverImage(variant)} availability={<DeliveryPromise sku={variant.sku} />} actions={<WishlistButton sku={variant.sku} />}/>;BusinessA business buyer's card: the brand and article number above the name, the pack size, the stock level, and at the foot the price excluding VAT per box with the first quantity that lowers it, beside a stepper and the add button.
<ProductCard product={variant} href={href} layout="business" packSize={packSizeOf(variant)} actions={ <> <QuantityStepper value={quantity} onValueChange={setQuantity} label={t("quantity")} … /> <CartAddProductButton sku={variant.sku} quantity={quantity} /> </> }/>;Business Stock LevelsA business card's stock as one status tag per level: in stock, low stock with the number, a pre-order and sold out, each in its own tone.
<ProductCard product={{ ...variant, stock: { level: "LOW_STOCK", lowStockQuantity: 3 } }} href={href} layout="business"/>;Price PlaceholderWhere the store hides prices from guests, the site shows a sign-in link in the price's place.
<ProductCard product={variant} href={href} layout="business" pricePlaceholder={signedIn ? undefined : <Link href="/sign-in">{t("sign-in-for-price")}</Link>}/>;
