Cart Line Item

Pro

A single cart row: image, title, variant, quantity stepper and a computed line total, with remove and save-for-later actions. Bursts of quantity clicks settle into one write, which rolls back if the server rejects it. Stock caps the stepper rather than only hinting, and a price that moved since the item went into the cart says so. Default and compact densities.

</>sv-cart-line-item cartecommercequantityline-itemcheckout

Complexity tier: Interactive The primitive bar, plus declared async states, a keyboard model, focus restoration and undo for destructive actions.

import { SvCartLineItemComponent } from '@styloviz-pro/commerce';

Demo & usage

Basic usage

Pro

Usage example — open the gallery for the full interactive demo.

Pro

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Read-only (order details)

Pro

Low-stock hint + save for later

Pro

Persisting one write per burst of clicks

Pro

Removed, with the undo window the host owns

Pro

A variant that stopped being buyable

Pro

Stock as a cap, and a cart that already holds too many

Pro

A price that moved since the item went into the cart

Pro

When to use

  • Rows in the cart page and the mini-cart drawer.
  • A read-only line in order details / confirmation (set `editable`/`removable` to false).
  • You want the line total to track quantity automatically.

Async states

  • error

Server writes

Writes
  • quantityCommit

Each payload carries a requestKey — send it with the request so a retry cannot apply the same change twice.

Rollback
Call event.rollback() when the save fails — the change is already on screen, and this is what puts it back.
While saving
Bind saving — with two writes outstanding there is no single state to roll back to, so the action stays locked until the first one lands.
On failure
Pass the failure to error to show it in place — without taking away what the user was working on.

Inputs

NameTypeDefaultDescription
titlerequiredstring—Product title.
unitPricerequirednumber—Unit price (minor units when `minorUnits` is true).
imagestring''Product image URL.
hrefstring''Link target for the product.
variantLabelstring''Variant description, e.g. "Black / M".
compareAtnumber | nullnullCompare-at unit price for a strikethrough.
currencystring'USD'ISO 4217 currency code.
localestring'en-US'BCP-47 locale.
minorUnitsbooleanfalseTreat prices as minor units.
minnumber1Minimum quantity.
maxnumber99Maximum quantity.
stocknumber | nullnullRemaining stock, as the server last reported it. `null` is "not known". THIS IS A CAP, NOT A HINT. Stock is the number most likely to change while the row is on screen — the cart was filled an hour ago and somebody else has been shopping since. It used to only produce a "Only 3 left" nudge below 6, which left the stepper happily climbing to 99 and a line total quoting a price for goods that do not exist. It now caps the stepper (`quantityMax`), and a quantity already above the cap gets said out loud rather than silently rewritten: the cart holds what it holds, and correcting it is a write this row does not make.
previousUnitPricenumber | nullnullWhat this item's unit price was when it went into the cart, if it has changed since. A PRICE THAT MOVES UNDER A SHOPPER HAS TO SAY SO. `unitPrice` is an input, so a host applying a fresh quote silently redraws the line total and the shopper is asked to pay a number they never agreed to — the same failure `sv-order-summary` handles with its `repriced` state, one level down. Set this to the old price and the row names the change and its direction. It is display-only: it does not affect the line total, which is always `unitPrice × quantity`.
editablebooleantrueShow the quantity stepper (false renders a static "Qty: n").
removablebooleantrueShow the remove control.
showSaveForLaterbooleanfalseShow a "Save for later" control.
sizeCartLineSize'default'Density.
showMediabooleantrueShow the product image (or its placeholder) at the start of the row. `false` is for lines where the shopper is not choosing by sight — a till ticket, a returns form, a re-order list. There the empty image box is 64px of row that carries nothing, on the one screen that needs every line it can fit.
savingbooleanfalseTrue while a write for this row is in flight. The stepper and both row actions stop responding, so a second edit cannot race the first and leave `rollback()` ambiguous.
errorSvDataError | string | nullnullThe last write for this row failed. Rendered as a danger note under the title. Roll the quantity back as well — this input reports the failure, it does not undo it.
commitDelayMsnumber400How long the quantity settles before it is reported, in milliseconds. `0` reports every step immediately, which is what the row used to do.
unavailablebooleanfalseThis variant can no longer be bought — discontinued, or out of stock since the cart was filled. The row still renders, because a shopper needs to see what they are being asked to drop, but the quantity is frozen and the line total is struck: it is not part of the payable order.
unavailableTextstring'No longer available'Copy for the unavailable note.
removedbooleanfalseThe host has removed this row and is offering a way back. The row renders as removed with an Undo control, and nothing else responds. The window is the host's to open and close — a component that started its own timer and committed the deletion when it expired would be claiming a write it never makes.
quantitynumber (two-way)1Quantity (two-way: `[(quantity)]`). Moves on every click so the stepper and the line total stay live. What to PERSIST comes from `quantityCommit`, once the clicking stops.

Outputs

EventPayload typeDescription
quantityCommitCartQuantityCommitEventThe quantity settled. One emission per burst of edits — persist this.
removevoidEmitted when the remove control is pressed.
undoRemovevoidThe shopper pressed Undo on a removed row.
saveForLatervoidEmitted when "Save for later" is pressed.
quantityChangenumberEmits the new `quantity` whenever it changes — the output half of `[(quantity)]`.

Customization & theming

  • Default and compact densities.
  • Editable quantity stepper or a static quantity.
  • Optional remove and save-for-later actions.
  • `commitDelayMs` tunes how long a burst of clicks settles; `0` reports every step.
  • `unavailableText` for why a variant can no longer be bought.
  • `stock` caps the stepper; `previousUnitPrice` turns on the repricing note, with the direction and old figure formatted in the row's own currency and locale.
  • Any ISO currency + locale, with minor-unit support.

Best practices

  • Do: Bind `[(quantity)]` for display and persist from `(quantityCommit)`. The model moves on every click; the commit fires once the clicking stops.
  • Do: Call `rollback()` from the commit payload when the write is rejected. It restores `previousQuantity` — the value the server last agreed to, not the value one click ago.
  • Do: Send `requestKey` with the write and ignore a key you have already applied, so a retry cannot charge for a quantity twice.
  • Do: Bind `[saving]` while a write for the row is in flight. It freezes the stepper and both actions, which is what keeps `rollback()` unambiguous.
  • Do: Pass `stock`. It is a cap, not a hint: the stepper stops at `min(max, stock)`, a low-stock note appears near the limit, and `0` makes the row unbuyable without a separate `[unavailable]`.
  • Do: Bind `previousUnitPrice` when a fresh quote changed the price. Otherwise the line total silently redraws and asks the shopper to pay a figure they never agreed to.
  • Do: Own the undo window yourself: soft-remove on `(remove)`, set `[removed]="true"`, and restore on `(undoRemove)`. The row will not start a timer and commit a deletion on your behalf.
  • Don't: Don't fire a request from `(quantityChange)` — three taps on `+` become three requests for 2, 3 and 4 against a server free to apply them in any order.
  • Don't: Don't compute the line total yourself — it is derived from unit price × quantity.
  • Don't: Don't expect the row to fix an over-stock quantity. It reports the conflict; clamping the cart is a write, and writes belong to you.
  • Tip: Call `commitNow()` from a Checkout button so a still-settling edit is not lost on navigation.

Accessibility

  • Remove, undo and save actions are real buttons with descriptive labels naming the product.
  • The quantity stepper is named after its line, so a five-item cart does not announce "Number input" five times — and its + / – buttons are named after the line too, not "Increase value".
  • Whichever note the row is showing describes the stepper via `aria-describedby`, with a per-instance id so one row is never described by another's. A quantity that outruns the stock, or a write that failed, also sets `aria-invalid` — so the red border and the announced state agree.
  • A write in flight sets `aria-busy` on the row and disables its controls.
  • A failed write is text in a `role="alert"`, never colour alone; removed and unavailable rows say so in words as well as by dimming the image. The repricing note names the direction in words, so up and down are not told apart by colour.
  • Image links and titles are keyboard-reachable; line total is tabular-nums text.

Feedback on Cart Line Item

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.