ComponentsSite blocks

Product card & grid

Product tiles for shops. ProductCard: the photo (a second photo on hover, or one photo per colour swatch), sale, new and sold-out badges, vendor and title (the title link covers the card), the price (sale price with the regular one struck through, or a range), star rating, colour swatches as native radios that swap the photo with CSS, and slots for quick add and wishlist. No cart logic inside. ProductGrid: a section with header and “Shop all”, 2–5 columns by container width, a list variant with the photo beside the text, an empty state and ItemList JSON-LD; bare renders only the list. productJsonLd builds the Product node for a product page.

Native radios + :has() blocks/site/product-grid.tsx, blocks/site/product-card.tsx, styles/commerce.csstheme · material · accent · density · font
Stacked plates and bowls in a pale blue glaze on a wooden table
−20%

Kilnhouse

Tide plates, set of 4

Now $96, Was $120

64 reviews
Options for Tide plates, set of 4
Cream cups with raw terracotta feet in a row
New

Kilnhouse

128 reviews
Options for Morning cup

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

import { ProductCard, ProductGrid, productJsonLd, JsonLd } from "@vamos-ui/react/blocks";

<ProductGrid
  title="New this *week*"
  columns={4}
  allLink={{ href: "/shop", label: "Shop all" }}
  products={[{
    title: "Morning cup", href: "/shop/morning-cup", image,
    price: { amount: "28", currency: "USD" }, compareAt: { amount: "34", currency: "USD" },
    rating: { value: 4.6, count: 128 },
    swatches: [{ name: "Clay foot", color: "#e8dcc8", image: clayPhoto }, { name: "Chalk", color: "#f4f1ea", image: chalkPhoto }],
  }]}
/>

// product page
<JsonLd data={productJsonLd(product, { url: "https://shop.example.com/shop/morning-cup", brand: "Kilnhouse" })} />

Put productJsonLd on the product page only, not on every card in a grid.

Examples

Grid section (4 columns)

Header, “Shop all”, sale / new / sold-out badges, a price range and a hover photo. Columns step 4 → 3 → 2 → 1 with the container.

Fresh from the kiln

New this week

Shop all
  • Stacked plates and bowls in a pale blue glaze on a wooden table
    −20%

    Kilnhouse

    Tide plates, set of 4

    Now $96, Was $120

    64 reviews
    Options for Tide plates, set of 4
  • Cream cups with raw terracotta feet in a row
    New

    Kilnhouse

    128 reviews
    Options for Morning cup

List variant (bare)

variant="list" puts the photo beside the text; bare drops Section and header. cardProps sets aspect, saleStyle and rangeStyle for every card.

Wishlist and quick-add slots

Your own controls on the photo. The built-in + button (onQuickAdd) is the alternative to the quickAdd slot.

Phone width

One column; quick add is always visible on touch screens.

New this week

  • Stacked plates and bowls in a pale blue glaze on a wooden table
    −20%

    Kilnhouse

    Tide plates, set of 4

    Now $96, Was $120

    64 reviews
    Options for Tide plates, set of 4
  • Cream cups with raw terracotta feet in a row
    New

    Kilnhouse

    128 reviews
    Options for Morning cup

API

ProductCard

PropTypeDefault
title / href / vendor
The title link covers the card; vendor sits above it.
string / string / ReactNode—
image / hoverImage
Photo, and a second photo shown on hover / focus.
SiteImage—
price / compareAt
compareAt above price → sale price, struck-through regular price, sale badge.
MoneyValue—
priceRange / rangeStyle
“From $54” or “$54 – $88”.
{ min; max } / "from" | "range"— / "from"
isNew / soldOut / badge
Badges top-start. soldOut also disables the built-in quick add.
boolean / boolean / ReactNode—
rating
StarRating, small.
{ value; count? }—
swatches / maxSwatches
Native radios; up to 8 swap the photo with CSS; more show “+N more”.
{ name; color; image?; soldOut? }[] / number— / 5
swatchIndex / defaultSwatchIndex / onSwatchChange
Controlled from a client component, or uncontrolled.
number / number / (i) => void—
quickAdd / onQuickAdd
Your own control, or a built-in round + button.
ReactNode / (swatchIndex) => void—
wishlist
A control top-end on the photo.
ReactNode—
onNavigate
Runs when the title link is followed.
(swatchIndex) => void—
layout / size / aspect / sizes / priority / headingLevel
Card shape and photo loading.
"stack" | "row" / "md" | "lg" / ImageAspect / string / boolean / 2 | 3 | 4"stack" / "md" / "4:5" / — / — / 3
saleStyle
Sale badge “−20%” or “Sale”.
"percent" | "label""percent"
labels / locale / components
Strings, money format, Image / Link adapters.
Partial<CommerceLabels> / string / BlockAdapters—

ProductGrid

PropTypeDefault
products
The cards.
ProductCardData[]—
variant / columns
Responsive columns by container width, or one row per product.
"grid" | "list" / 2 | 3 | 4 | 5"grid" / 4
eyebrow / title / lead / allLink
Section header; the title also names the list.
ReactNode / SiteLink—
cardProps / renderCard
Shared card props, or render each card yourself.
Partial<ProductCardProps> / (product, i) => ReactNode—
priorityCount
Eager photos for the first row.
number0
bare / listLabel
Only the list (no section); its name when there is no title.
boolean / string—
empty
Empty state.
{ title?; text?; action? }—
jsonLd / siteUrl
ItemList JSON-LD of product URLs.
boolean / stringfalse
theme / anchor / id / className / headingLevel / components / editMode
Shared block props.
BlockBaseProps—

productJsonLd(product, options)

PropTypeDefault
options
Product with Offer / AggregateOffer, availability and AggregateRating.
{ url?; sku?; brand?; description?; images? }—