All Taste components
Mobile screens
Cart & checkout
A food order before payment: items with quantity steppers, an applied coupon, the bill in rupees and a pay button.
veg / non-veg marks · qty steppers · coupon applied · itemised bill · payment method + pay bar
Chicken biryani
Full · extra raita · ₹349
1
Paneer tikka
8 pieces · ₹279 × 2
2
Gulab jamun
2 pieces · ₹99
1
SAVE60 appliedYou saved ₹60
Bill details
Item total₹1,006.00
Coupon SAVE60−₹60.00
Delivery fee₹39.00
Platform fee₹6.00
GST & restaurant charges₹50.30
To pay₹1,041.30
// Mobile-first page; the same markup maps to React Native via NativeWind (ScrollView + sticky footer View).
"use client";
import { useState } from "react";
import { BadgePercent, ChevronDown, ChevronLeft, Minus, Plus } from "lucide-react";
import { cn } from "@/lib/utils";
type Item = { id: string; name: string; note: string; price: number; qty: number; veg: boolean };
const restaurant = { name: "Kaveri Biryani House" };
const initialItems: Item[] = [
{ id: "biryani", name: "Chicken biryani", note: "Full · extra raita", price: 349, qty: 1, veg: false },
{ id: "paneer", name: "Paneer tikka", note: "8 pieces", price: 279, qty: 2, veg: true },
{ id: "jamun", name: "Gulab jamun", note: "2 pieces", price: 99, qty: 1, veg: true },
];
const coupon = { code: "SAVE60", off: 60 };
const fees = { delivery: 39, platform: 6, taxRate: 0.05 };
const inr = (n: number) => "₹" + n.toLocaleString("en-IN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
export default function CartScreen() {
const [items, setItems] = useState(initialItems);
const [applied, setApplied] = useState(true);
const change = (id: string, by: number) =>
setItems((list) => list.map((i) => (i.id === id ? { ...i, qty: i.qty + by } : i)).filter((i) => i.qty > 0));
const itemTotal = items.reduce((s, i) => s + i.price * i.qty, 0);
const discount = applied ? coupon.off : 0;
const taxes = Math.round(itemTotal * fees.taxRate * 100) / 100;
const toPay = itemTotal - discount + fees.delivery + fees.platform + taxes;
const bill = [
{ label: "Item total", value: inr(itemTotal) },
...(applied ? [{ label: "Coupon " + coupon.code, value: "−" + inr(discount), save: true }] : []),
{ label: "Delivery fee", value: inr(fees.delivery) },
{ label: "Platform fee", value: inr(fees.platform) },
{ label: "GST & restaurant charges", value: inr(taxes) },
];
return (
<div className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg font-sans text-fg">
<header className="flex h-12 items-center gap-1 px-2">
<button type="button" aria-label="Back" className="grid size-11 place-items-center rounded-full hover:bg-surface-2"><ChevronLeft className="size-[22px]" /></button>
<div className="flex-1 text-center leading-tight">
<h1 className="text-[15px] font-semibold">Your cart</h1>
<p className="text-xs text-fg-muted">{restaurant.name}</p>
</div>
<span className="size-11" />
</header>
<main className="flex-1 px-4 pb-4">
<ul aria-label="Items" className="divide-y divide-border">
{items.map((i) => (
<li key={i.id} className="flex items-center gap-2.5 py-2.5">
<span role="img" aria-label={i.veg ? "Vegetarian" : "Non-vegetarian"}
className={cn("mt-[3px] grid size-3.5 shrink-0 place-items-center self-start rounded-[3px] border-[1.5px]", i.veg ? "border-success" : "border-danger")}>
<span className={cn("size-1.5 rounded-full", i.veg ? "bg-success" : "bg-danger")} />
</span>
<div className="min-w-0 flex-1">
<p className="font-medium">{i.name}</p>
<p className="text-xs tabular-nums text-fg-muted">{i.note} · ₹{i.price}{i.qty > 1 ? " × " + i.qty : ""}</p>
</div>
<div className="grid shrink-0 justify-items-end gap-1.5">
<div className="inline-flex h-9 items-center rounded border border-border-strong">
<button type="button" onClick={() => change(i.id, -1)} aria-label={"Remove one " + i.name} className="grid size-[34px] place-items-center text-primary"><Minus className="size-4" /></button>
<span className="min-w-[18px] text-center text-sm font-semibold tabular-nums">{i.qty}</span>
<button type="button" onClick={() => change(i.id, 1)} aria-label={"Add one " + i.name} className="grid size-[34px] place-items-center text-primary"><Plus className="size-4" /></button>
</div>
</div>
</li>
))}
</ul>
{applied && (
<div className="mt-3 flex items-center gap-2.5 rounded-lg border border-dashed border-success bg-success-soft py-2.5 pl-3 pr-1 text-success-fg">
<BadgePercent className="size-[18px] shrink-0" aria-hidden />
<div className="leading-tight">
<p className="text-[13.5px] font-semibold">{coupon.code} applied</p>
<p className="text-xs tabular-nums">You saved ₹{coupon.off}</p>
</div>
<button type="button" onClick={() => setApplied(false)} className="ml-auto h-11 rounded px-2.5 text-[13px] font-medium">Remove</button>
</div>
)}
<h2 className="mt-4 text-[15px] font-semibold">Bill details</h2>
<dl className="mt-2 grid gap-2 text-[13.5px]">
{bill.map((b) => (
<div key={b.label} className="flex justify-between gap-3">
<dt className="text-fg-muted">{b.label}</dt>
<dd className={cn("tabular-nums", b.save && "text-success-fg")}>{b.value}</dd>
</div>
))}
<div className="flex justify-between gap-3 border-t border-border pt-2.5 text-[15px] font-semibold">
<dt>To pay</dt><dd className="tabular-nums">{inr(toPay)}</dd>
</div>
</dl>
</main>
<footer className="sticky bottom-0 flex items-center gap-3 border-t border-border bg-bg px-4 pb-[max(env(safe-area-inset-bottom),16px)] pt-3">
<button type="button" aria-label="Change payment method" className="grid min-w-0 flex-1 text-left leading-tight">
<span className="text-[11px] font-medium text-fg-muted">Pay using UPI</span>
<span className="inline-flex items-center gap-0.5 whitespace-nowrap text-[13.5px] font-semibold">priya@upi <ChevronDown className="size-3.5" /></span>
</button>
<button type="button" disabled={!items.length} className="h-12 rounded bg-primary px-5 text-[15px] font-semibold tabular-nums text-primary-fg hover:bg-primary-hover disabled:opacity-50">
Pay {inr(toPay)}
</button>
</footer>
</div>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.