KQ-1332 · CART/DRAWER
Cart Drawer
A bag that slides over the page it belongs to: the panel arrives from the right edge on glide over a fading scrim and can be pulled shut by its header, 1:1 with a rubber-banded overpull and a release or flick that closes on glide with the throw's velocity. Each line's face slides over a tray of actions — Save and Remove, or in full mode a pull right through that removes it — a removed line leaves on the exit ease while its height closes on glide and Undo keeps it for six seconds, steppers roll their counts on snap, and the totals roll, count or cross-fade as anything changes. A bar fills toward free delivery on glide with a van on its leading edge, and when the visitor carries the subtotal over the threshold the bar turns success, flecks spray from its end and the news lands on recoil; Escape closes a tray then the drawer, and every swipe has a button that does the same thing.
Your bag
3 items in the bag:€9.00 away from free delivery
- Subtotal
- €51.00
- Savings
- €2.00
- Delivery
- €4.95
- Total
- €55.95
3 items · €51.00 · €9.00 away from free delivery
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/cart-drawer
Props
- items / defaultItems
- CartLine[]
- default — / defaultCartItems
- Controlled or initial lines: name, variant, unit price, compare-at price, quantity, max, and a thumbnail or drawn art.
- onItemsChange
- (items: CartLine[]) => void
- default —
- Fires from the stepper, swipe, button, Undo or upsell that changed the bag, with every line.
- open / defaultOpen
- boolean
- default — / false
- Controlled or initial: the drawer is open.
- onOpenChange
- (open: boolean) => void
- default —
- Fires from the close button, Escape, the scrim or the pull that closed it.
- upsells
- CartUpsell[]
- default defaultCartUpsells
- Things that pair with the bag, offered in a sideways row; ones already in the bag are left out.
- onUpsellAdd
- (upsell: CartUpsell) => void
- default —
- An upsell was added; it is already at the top of the lines.
- onRemove
- (line: CartLine) => void
- default —
- A line left the bag: swiped, removed, or stepped below one.
- onUndo
- (line: CartLine) => void
- default —
- Undo put a removed line back where it was.
- onSaveForLater
- (line: CartLine) => void
- default —
- Save for later was chosen from a line's tray; it has left the bag.
- onCheckout
- (items: CartLine[], total: number) => void | Promise<void>
- default —
- Checkout was pressed. A promise holds the button pending; a rejection's message is shown.
- threshold
- number
- default 60
- The subtotal that earns free delivery, in the currency's major unit.
- shipping
- number
- default 4.95
- Delivery under the threshold.
- swipe
- "reveal" | "full" | "off"
- default "reveal"
- How a line is pulled away: open a tray of actions, pull right through to remove, or not at all.
- totals
- "roll" | "count" | "fade"
- default "roll"
- How changing figures move: digits that roll, figures that count through the cents, or a cross-fade.
- format
- (amount: number) => string
- default euros
- Formats an amount for display.
- title
- string
- default "Your bag"
- The drawer's heading and accessible name.
- checkoutLabel
- string
- default "Checkout"
- The checkout button's words.
- emptyTitle
- string
- default "Your bag is empty"
- The empty bag's heading.
- emptyBody
- string
- default —
- The empty bag's line.
- onContinue
- () => void
- default —
- Continue shopping was pressed on the empty bag (it also closes the drawer).
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholder lines; error offers Retry.
- onRetry
- () => void
- default —
- The Retry button of the error state.
- children
- ReactNode
- default —
- The page the drawer opens over; it is inert while the drawer is open.
- label
- string
- default —
- The dialog's accessible name when it should differ from the title.
- sound
- boolean
- default false
- Pops on the steppers and upsells, a brighter one when free delivery unlocks and a low one as a full swipe arms; swishes as the drawer and lines come and go.
- disabled
- boolean
- default false
- Shows the bag but takes no input.
- className
- string
- default —
- Classes for the box the drawer lives in; give it a height.
| Prop | Type | Default | Description |
|---|---|---|---|
| items / defaultItems | CartLine[] | — / defaultCartItems | Controlled or initial lines: name, variant, unit price, compare-at price, quantity, max, and a thumbnail or drawn art. |
| onItemsChange | (items: CartLine[]) => void | — | Fires from the stepper, swipe, button, Undo or upsell that changed the bag, with every line. |
| open / defaultOpen | boolean | — / false | Controlled or initial: the drawer is open. |
| onOpenChange | (open: boolean) => void | — | Fires from the close button, Escape, the scrim or the pull that closed it. |
| upsells | CartUpsell[] | defaultCartUpsells | Things that pair with the bag, offered in a sideways row; ones already in the bag are left out. |
| onUpsellAdd | (upsell: CartUpsell) => void | — | An upsell was added; it is already at the top of the lines. |
| onRemove | (line: CartLine) => void | — | A line left the bag: swiped, removed, or stepped below one. |
| onUndo | (line: CartLine) => void | — | Undo put a removed line back where it was. |
| onSaveForLater | (line: CartLine) => void | — | Save for later was chosen from a line's tray; it has left the bag. |
| onCheckout | (items: CartLine[], total: number) => void | Promise<void> | — | Checkout was pressed. A promise holds the button pending; a rejection's message is shown. |
| threshold | number | 60 | The subtotal that earns free delivery, in the currency's major unit. |
| shipping | number | 4.95 | Delivery under the threshold. |
| swipe | "reveal" | "full" | "off" | "reveal" | How a line is pulled away: open a tray of actions, pull right through to remove, or not at all. |
| totals | "roll" | "count" | "fade" | "roll" | How changing figures move: digits that roll, figures that count through the cents, or a cross-fade. |
| format | (amount: number) => string | euros | Formats an amount for display. |
| title | string | "Your bag" | The drawer's heading and accessible name. |
| checkoutLabel | string | "Checkout" | The checkout button's words. |
| emptyTitle | string | "Your bag is empty" | The empty bag's heading. |
| emptyBody | string | — | The empty bag's line. |
| onContinue | () => void | — | Continue shopping was pressed on the empty bag (it also closes the drawer). |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholder lines; error offers Retry. |
| onRetry | () => void | — | The Retry button of the error state. |
| children | ReactNode | — | The page the drawer opens over; it is inert while the drawer is open. |
| label | string | — | The dialog's accessible name when it should differ from the title. |
| sound | boolean | false | Pops on the steppers and upsells, a brighter one when free delivery unlocks and a low one as a full swipe arms; swishes as the drawer and lines come and go. |
| disabled | boolean | false | Shows the bag but takes no input. |
| className | string | — | Classes for the box the drawer lives in; give it a height. |