Cart line item
Cart row with thumb, variant chip, stepper, line price with compare-at and a hover-reveal remove.
componentrowanycartquantitycompare-at
$
npx shadcn@latest add @storekit/cart-line-itemcart-line-item
Live and interactive. Switch the brand in the frame header to see the same source under another token file.
Installation
$
npx shadcn@latest add @storekit/cart-line-item$
pnpm dlx shadcn@latest add @storekit/cart-line-item$
yarn dlx shadcn@latest add @storekit/cart-line-item$
bunx --bun shadcn@latest add @storekit/cart-line-itemSource
components/storekit/cart/cart-line-item.tsx
71 lines
"use client"
import { X } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"
import { Price } from "@/components/storekit/ui/price"
import { ProductImage } from "@/components/storekit/ui/product-image"
import { Stepper } from "@/components/storekit/ui/stepper"
/* One cart row: thumb, title, variant chip, quantity stepper, line price with
compare-at, and a remove button. On pointer devices the remove button
reveals on hover or focus; on touch it is always visible. */
export function CartLineItem({
title = "Daily Greens",
variant = "30 servings",
image = "/brands/morrow/greens.webp",
price = 5900,
compareAt,
quantity = 1,
max = 10,
onQuantityChange,
onRemove,
className,
}: {
title?: string
variant?: string
image?: string
/** Unit price in cents. */
price?: number
/** Unit compare-at in cents. */
compareAt?: number
quantity?: number
max?: number
onQuantityChange?: (next: number) => void
onRemove?: () => void
className?: string
}) {
return (
<div data-sk-section="cart" data-sk-variant="cart-line-item" className={cn("group relative flex gap-3 py-4 sm:gap-4", className)}>
<ProductImage src={image} alt={title} className="h-[72px] w-[72px] shrink-0 rounded-sk-card border border-sk-line sm:h-24 sm:w-24" fit="cover" zoomOnHover={false} />
<div className="flex min-w-0 flex-1 flex-col justify-between gap-3">
<div className="flex items-start justify-between gap-3 pr-10">
<div className="min-w-0">
<p className="truncate text-[15px] font-medium text-sk-ink">{title}</p>
{variant ? (
<span className="mt-1 inline-flex rounded-sk-chip border border-sk-line px-2 py-0.5 text-xs text-sk-muted">{variant}</span>
) : null}
</div>
</div>
<div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-2">
<Stepper label={`Quantity, ${title}`} value={quantity} onChange={(q) => onQuantityChange?.(q)} max={max} size="sm" className="shrink-0" />
<Price amount={price * quantity} compareAt={compareAt ? compareAt * quantity : undefined} size="sm" className="ml-auto min-w-0 justify-end text-right" />
</div>
</div>
<button
type="button"
aria-label={`Remove ${title}`}
onClick={onRemove}
className={cn(
"absolute right-0 top-2 grid h-11 w-11 place-items-center rounded-sk-btn text-sk-muted",
"transition-[opacity,transform,background-color,color] duration-200 ease-sk-out hover:bg-sk-ink/[0.05] hover:text-sk-ink active:scale-[0.97]",
"focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper",
"[@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover:opacity-100 [@media(hover:hover)]:group-focus-within:opacity-100"
)}
>
<X size={16} />
</button>
</div>
)
}
Usage
Import from "@/components/storekit/cart/cart-line-item" and pass your product data. The demo above exercises the full prop surface with sample values.

