UI componentsdocs/ui/components/commerce.md

Commerce & billing

Products, carts, prices, plans and invoices.

← Component reference

Product & price #

Product tiles and prices.

ProductCard #

Product tile.

Terracotta stoneware mugSale

Stoneware mug

$32.00$24.00

In cart: 0

function Example() {
    const inCart = signal(0);
    return (
        <Stack gap="0.5rem">
            <ProductCard
                title="Stoneware mug"
                order={3}
                price={24}
                strike={32}
                badge="Sale"
                image="/images/mug.jpg"
                imageAlt="Terracotta stoneware mug"
                onAdd={() => inCart.set(inCart() + 1)}
            />
            <Text muted>In cart: {inCart()}</Text>
        </Stack>
    );
}

Slots: root badge body footer image media title

PropTypeRequiredDefaultDescription
pricenumberyesPrice as a number.
titlestringyesProduct name.
addLabelcontent"Add"Label for the add button (default "Add").
badgecontentBadge over the image, e.g. "Sale".
currencystringISO currency code for formatting.
imagestringProduct image URL.
imageAltstring""Alt text for the product image (default: decorative).
onAdd() => voidShows an add-to-cart button; called when it is pressed.
orderHeadingLevel4Heading level of the title, to fit the page outline. Default 4.
strikenumberPrevious price, shown struck through.

HeadingLevel

type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;

Price #

Formatted price with optional strike-through and period.

$32.00$24.00/month
<Price amount={24} strike={32} period="month" />

Slots: root amount period strike

PropTypeRequiredDefaultDescription
amountnumberyesPrice as a number.
currencystring"USD"ISO currency code for formatting, e.g. USD.
periodstringBilling period after the price, e.g. month.
strikenumberPrevious price, shown struck through.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Cart #

Cart lines and order totals.

CartLine #

Cart row.

Stoneware mug

$24.00
2
function Example() {
    const quantity = signal(2);
    const removed = signal(false);
    return (
        <Show
            when={!removed()}
            fallback={
                <Button size="sm" variant="outline" onClick={() => removed.set(false)}>
                    Undo remove
                </Button>
            }
        >
            <CartLine
                title="Stoneware mug"
                price={24}
                quantity={quantity()}
                image="/images/mug.jpg"
                onQuantityChange={quantity.set}
                onRemove={() => removed.set(true)}
            />
        </Show>
    );
}

Slots: root body image quantity remove title

PropTypeRequiredDefaultDescription
pricenumberyesUnit price as a number.
quantitynumberyesQuantity in the cart.
titlestringyesProduct name.
currencystringISO currency code for formatting.
imagestringProduct thumbnail URL.
onQuantityChange(value: number) => voidShows quantity buttons; called with the new quantity.
onRemove() => voidShows a remove button; called when it is pressed.

OrderSummary #

Subtotal / tax / total block.

Subtotal$48.00
ShippingFree
Tax$4.32
Total$52.32
<OrderSummary
    lines={[
        { label: "Subtotal", value: "$48.00" },
        { label: "Shipping", value: "Free", muted: true },
        { label: "Tax", value: "$4.32" },
    ]}
    total="$52.32"
/>

Slots: root row total

PropTypeRequiredDefaultDescription
linesOrderSummaryLine[]yesSummary lines above the total.
totalcontentyesTotal amount.
childrencontentContent below the total, e.g. a checkout button.
totalLabelcontent"Total"Label for the total row (default "Total").

OrderSummaryLine

FieldTypeRequiredDescription
labelstringyesLine label, e.g. "Shipping".
valuestringyesLine amount text.
mutedbooleanDe-emphasise the line.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Plans #

Pricing cards and feature comparisons.

PricingCard #

Pricing plan card with price, features and a call to action.

Pro

$20/per seat / month

For growing teams.

  • Unlimited projects
  • 10,000 build minutes
  • Email support
Interact with the example: callbacks show up here.
<PricingCard
    name="Pro"
    price="$20"
    period="per seat / month"
    description="For growing teams."
    features={["Unlimited projects", "10,000 build minutes", "Email support"]}
    highlighted
    action={<Button onClick={() => {}}>Start trial</Button>}
/>

Slots: root action description features name period price

PropTypeRequiredDefaultDescription
namestringyesPlan name.
pricestringyesPrice text, e.g. $20.
actioncontentCall to action at the bottom, e.g. a Button.
descriptionstringShort pitch under the name.
featuresstring[]Features included in the plan.
highlightedbooleanEmphasise this plan (e.g. the recommended one).
periodstringBilling period after the price, e.g. per month.

FeatureList #

List of features with check icons.

  • Unlimited projects
  • Preview deploys
  • SSO & audit log
<FeatureList items={["Unlimited projects", "Preview deploys", "SSO & audit log"]} />

Slots: root icon item label

PropTypeRequiredDefaultDescription
itemsstring[]yesFeatures, one per line.
iconIconName"check"Icon before each feature.

IconName — Material Design Icons path names used by <Icon />.

type IconName = | "check" | "x" | "plus" | "minus" | "search" | "user" | "users" | "settings" | "menu" | "home" | "heart" | "star" | "bell" | "mail" | "calendar" | "clock" | "edit" | "trash" | "copy" | "download" | "upload" | "link" | "external" | "info" | "warning" | "error" | "success" | "chevron-down" | "chevron-up" | "chevron-left" | "chevron-right" | "arrow-left" | "arrow-right" | "eye" | "eye-off" | "eye-outline" | "lock" | "unlock" | "filter" | "more" | "close" | "spinner" | "sun" | "moon" | "play" | "pause" | "refresh" | "share" | "image" | "file" | "folder" | "zap" | "phone" | "git" | "code";

FeatureCompare #

Plan comparison grid (ARIA table over CSS grid rows).

Feature
Free
Pro
Enterprise
Projects
3
Unlimited
Unlimited
Preview deploys
SSO
—
—
<FeatureCompare
    plans={["Free", "Pro", "Enterprise"]}
    rows={[
        { feature: "Projects", values: ["3", "Unlimited", "Unlimited"] },
        { feature: "Preview deploys", values: [true, true, true] },
        { feature: "SSO", values: [false, false, true] },
    ]}
/>

Slots: root cell head row

PropTypeRequiredDefaultDescription
plansstring[]yesPlan names (the columns).
rowsFeatureCompareRow[]yesFeatures (the rows).
featureLabelcontent"Feature"Header of the first column (default "Feature").

FeatureCompareRow

FieldTypeRequiredDescription
featurestringyesFeature name.
values(string | boolean)[]yesOne cell per plan: true / false for a check or cross, or text.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Billing #

Payment methods and invoices.

CreditCardPreview #

Masked payment card preview.

Visa

•••• •••• •••• 4242

Ada Lovelace08/29
<CreditCardPreview brand="Visa" last4="4242" exp="08/29" name="Ada Lovelace" />

Slots: root bottom brand number top

PropTypeRequiredDefaultDescription
last4stringyesLast four digits.
brandstring"Card"Card network, e.g. Visa.
expstringExpiry, e.g. 08/29.
namestring"Cardholder"Cardholder name.

InvoiceRow #

Invoice row with number, date, amount and status.

Interact with the example: callbacks show up here.
<InvoiceRow
    id="INV-2026-014"
    date="Sep 1, 2026"
    amount="$49.00"
    status="paid"
    onClick={() => {}}
/>

Slots: root amount date number status

PropTypeRequiredDefaultDescription
amountstringyesAmount text, e.g. $49.00.
datestringyesInvoice date text.
idstringyesInvoice number shown in the row. Also forwarded as the element id.
statusInvoiceStatusyesPayment state; sets the badge.
onClick() => voidMakes the row a button, e.g. to open the invoice.

InvoiceStatus

type InvoiceStatus = "paid" | "open" | "void" | "past_due";

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.