Product Card

Pro

A product an assistant found for you, in its reply. The photo sits inset in the card, the store and the price are one glance apart, and a line from the assistant says why this one fits. The whole card links out to the store.

What's the best stoneware bowl for everyday use, under $60?

My pick is East Fork's speckled set. It is the most reviewed bowl in your budget and goes in the dishwasher and the microwave.

Two speckled white stoneware bowls stacked on a dark tableBest seller
East Fork

Speckled Cereal Bowls, Set of 2

1,284 reviews

$48

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add product-card

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

Two blocks. The media frame, with the badge and the save toggle anchored to its corners. The content column: store, title, rating and price. Drop any part you do not have; the card closes up around the rest.

import {  ProductCard,  ProductCardMedia,  ProductCardBadge,  ProductCardSave,  ProductCardContent,  ProductCardStore,  ProductCardTitle,  ProductCardRating,  ProductCardPrice,} from "@/components/ui/product-card"<ProductCard>  <ProductCardMedia>    <ProductCardBadge />    <ProductCardSave />  </ProductCardMedia>  <ProductCardContent>    <ProductCardStore />    <ProductCardTitle />    <ProductCardRating />    <ProductCardPrice />  </ProductCardContent></ProductCard>

Default#

Hover hints at the lift: the card rises 2px and its shadow grows a step, nothing more. The link covers the whole card, so a click anywhere opens the store in a new tab, and keyboard focus rings the card itself.

Three pale green glazed bowls seen from above on marble-20%
Food52

Celadon Pasta Bowls, Set of 4

586 reviews

Now $52, was $65

Badge#

ProductCardBadge is one short label on the photo: a deal, a best seller, handmade. Leave it out when nothing stands out; a badge on every card stops meaning anything.

Compare-at price#

Pass the list price as compareAt and the card shows the discount: the price takes the sale tone and the old one follows it, struck through. Both are formatted for the locale and set in tabular figures, so a row of prices lines up.

Three pale green glazed bowls seen from above on marble-20%
Food52

Celadon Pasta Bowls, Set of 4

586 reviews

Now $52, was $65

Saved#

The heart is a toggle. Saved, the glyph fills in at full ink rather than turning red or brand, the same way every selected state in Koala reads. The fill grows out of the outline and resolves from a blur, so the change is felt without a bounce.

Horizontal#

orientation="horizontal" puts a square thumbnail beside the details. Use it for a single pick in a reply or a short list, where a column of tall cards would push the answer off screen.

Two speckled white stoneware bowls stacked on a dark tableBest seller
East Fork

Speckled Cereal Bowls, Set of 2

1,284 reviews

$48

Three pale green glazed bowls seen from above on marble-20%
Food52

Celadon Pasta Bowls, Set of 4

586 reviews

Now $52, was $65

Loading#

While a result streams in, ProductCardSkeleton holds its place in the same frame, with the photo and the lines where they will land.

API reference#

ProductCard

The card and the context every part reads. With href the whole card links out through one overlay link, labelled by the title and the store. Every part accepts className, merged last, and every part is droppable. Forwards native <article> props.

PropTypeDefaultDescription
hrefstring-The product page. Makes the whole card a link and turns on the hover lift.
targetReact.HTMLAttributeAnchorTarget"_blank"Where the link opens. A new tab by default, announced in the link's name, with rel="noopener noreferrer".
linkLabelstring-Overrides the link's accessible name, which is otherwise the title, the store and "opens in a new tab".
orientation"vertical" | "horizontal""vertical"vertical stacks the photo over the details, for a row of results. horizontal puts a square thumbnail beside them, for a single pick or a list.
Data attributeDescription
data-orientationThe card's orientation.

ProductCardMedia

The photo, inset 8px into the card with a concentric radius and a hairline edge. Square by default; the frame owns the ratio, so pass an aspect-* class to change it. Badge and Save anchor to it. Forwards native <div> props.

PropTypeDefaultDescription
srcstring-The photo, cropped to fill the frame. Any host works: the card renders a plain <img>.
altstring""Describe the product: color, material, what is in the shot.
srcSetstring-Responsive sources, so the browser picks one the width of the card.
sizesstring"(min-width: 768px) 240px, 60vw"Used with srcSet.
loading"eager" | "lazy""eager"Eager by default: a card slid into a carousel is moved by a transform, which the browser does not count as scrolling it into view, so a lazy photo would stay blank.
childrenReact.ReactNode-The overlays, or your own <img> or <video> instead of src.

ProductCardBadge

A pill over the top-left corner of the photo: a deal, a best seller, handmade. A Badge in the overlay tone, so it forwards Badge props.

PropTypeDefaultDescription
variantBadgeProps["variant"]"overlay"The tone. overlay reads on any photo.
size"sm" | "md" | "lg""sm"The Badge size.

ProductCardSave

The heart in the top-right corner of the photo. A Radix Toggle, so it is a button with aria-pressed, and its name is the action plus the product title. Saved reads in ink, never in brand: the glyph fills at full strength.

PropTypeDefaultDescription
savedboolean-Controlled saved state.
defaultSavedbooleanfalseInitial saved state, uncontrolled.
onSavedChange(saved: boolean) => void-Called when the heart is toggled.
labelstring"Save"The action's name while unsaved.
savedLabelstring"Saved"The name while saved.
Data attributeDescription
data-stateon while saved, off otherwise.

ProductCardContent

The details column under the photo, or beside it when horizontal. Forwards native <div> props.

ProductCardStore

Where it is sold: a 16px favicon and the store name, in the quiet eyebrow tone. A favicon that fails to load falls back to a storefront glyph. Forwards native <div> props.

PropTypeDefaultDescription
faviconstring-The store's favicon URL.
logoReact.ReactNode-Your own mark instead of a favicon, at 16px.

ProductCardTitle

The product name, clamped to two lines. Labels the card's link. Forwards native heading props.

PropTypeDefaultDescription
as"h2" | "h3" | "h4" | "p""h3"The element, to fit the heading outline of the page.

ProductCardRating

The star Rating, read only, with the average and the review count beside it, all in tabular-nums. Announced as one phrase. Forwards native <div> props.

PropTypeDefaultDescription
value*number-The average rating. Fractions fill part of a star.
countnumber-The number of reviews, formatted with thousands separators.
maxnumber5The number of stars.
localestring"en-US"The locale the count is formatted in.

ProductCardPrice

The price in tabular-nums. With a higher compareAt, the price turns to the sale tone and the old one sits after it struck through; screen readers hear "Now" and "was". Forwards native <p> props.

PropTypeDefaultDescription
pricenumber-The current price.
compareAtnumber-The price before the discount. Ignored unless it is higher than price.
currencystring"USD"An ISO 4217 code.
localestring"en-US"The locale the prices are formatted in. Whole amounts drop the cents.
childrenReact.ReactNode-Your own price text instead ("From $28").

ProductCardSkeleton

The card's shape while a result is still on its way: the same frame, inset photo and lines, shimmering. aria-hidden; announce the wait on the list around it. Forwards native <div> props.

PropTypeDefaultDescription
orientation"vertical" | "horizontal""vertical"Match the cards it stands in for.

FAQ#

Use Product Carousel. It lays Product Cards out in a row that snaps card by card, with the next one peeking, arrows in a small header, drag, swipe and the arrow keys, and skeletons for results still on their way.