Cart

A basket with quantities and per-line totals. Stock limits are enforced at the stepper, not on submit — letting someone add a ninth of eight units and saying so at checkout is the worst possible order.

src/components/ui/cart.tsx

About

From the component's own source, where it was written.

A basket: lines, quantities, per-line totals.

Amounts are integer minor units, matching LedgerTable and MoneyInput. Money that has been through a float has already lost — 0.1 + 0.2 is 0.30000000000000004, and a basket is exactly where that surfaces.

Each line shows its unit price *and* its line total once quantity exceeds one. Showing only the total makes it impossible to check, and showing only the unit price makes the sum look wrong.

Quantity controls are stepper buttons rather than a free number field. A cart quantity is realistically 1–10, and a text input invites "12" where "1" and a stray keystroke were meant. Remove is separate from decrement, so reaching zero is never how you delete something.

Stock limits are enforced at the control, not on submit. Letting someone add a ninth of eight units and telling them at checkout is the worst order.

Install

npx astralyx-ui add cart

Usage

import { Cart } from '@/components/ui/cart'

<Cart lines={lines} onQuantityChange={setQuantity} onRemove={remove} />

Composer

Change the props and the source below follows.

  • Aeron chair — remastered

    Size B · Graphite

    1
    £1,495.00 each£1,495.00
  • Monitor arm, single

    Silver

    2
    £219.00 each£438.00

    Only 2 in stock.

  • Desk mat

    Charcoal, 90×40

    1
    £45.00 each£45.00
Props
tsx
<Cart lines={lines} onQuantityChange={setQuantity} onRemove={remove} />

Examples

Live basket

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Cart

A basket: lines, quantities, per-line totals.

Extends Omit<ComponentProps<'div'>, 'children'>.

PropTypeDefaultDescription
linesCartLine[]required{ id, name, price, quantity, variant?, image?, max?, note? }. Price is minor units per unit.
currencystring'USD'—
localestring'en-GB'—
onQuantityChange(id: string, quantity: number) => void——
onRemove(id: string) => void—Separate from decrementing, so reaching zero is never how something gets deleted.
emptyReactNode——
removeLabelstring'Remove from basket'—
decreaseLabel(item: string) => stringDEFAULT_DECREASE_LABELAccessible names for the quantity stepper, given the line's name.
increaseLabel(item: string) => stringDEFAULT_INCREASE_LABEL—
eachLabelReactNode'each'Follows the unit price.
stockNote(max: number) => ReactNodeDEFAULT_STOCK_NOTEShown once a line hits its stock cap.

Types

CartLine

FieldTypeDescription
idstring—
nameReactNode—
pricenumberUnit price in minor units.
quantitynumberNot a free number field. A cart quantity is realistically 1–10, and a text input invites "12" where "1" was meant.
image?ReactNodeSmall square image.
variant?ReactNode—
max?numberCaps the increment control and explains why in words underneath.
note?ReactNode—

Behaviour

TopicDetailDescription
unit and line totalboth shownOnce quantity exceeds one. Showing only the total makes it uncheckable; showing only the unit price makes the sum look wrong.