OrderHistory
Past orders with number, date, status and total, each opening on <details> to show its items and links.
Demo
Order #10482Shipped$113.00, 2 items
Trail runner$89.00
Wool beanie$24.00
Order #10477Delivered$97.50, 3 items
Insulated bottle, 750 ml$32.50
Order #10391Refunded$149.00, 1 item
Order #10120Cancelled¥2,400, 1 item
- Merino socks, 3 pairs¥2,400
No orders yet
Get it
It needs theme.qwe, as every component does, and price.qwe; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/price.qwe$ curl -O https://quillolang.org/components/ui/order_history.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/order_history.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/order_history.qwe
let order_history_demo_orders = [ { id: "10482", date: "2026-10-20", status: "shipped", total: 113, href: "#order_history", tracking_href: "#order_tracker", items: [{ name: "Trail runner", quantity: 1, price: 89, image: "https://picsum.photos/seed/runner/80/80", options: ["Size 42", "Black"] }, { name: "Wool beanie", quantity: 1, price: 24, image: "https://picsum.photos/seed/beanie/80/80" }] }, { id: "10477", date: "2026-10-12", status: "delivered", total: 97.5, href: "#order_history", items: [{ name: "Insulated bottle, 750 ml", quantity: 3, price: 32.5, image: "https://picsum.photos/seed/bottle/80/80" }] }, { id: "10391", date: "2026-09-02", status: "refunded", total: 149, href: "#order_history", note: "Refunded to your card on 9 Sep.", items: [{ name: "Rain jacket", quantity: 1, price: 149 }] }, { id: "10120", date: "2026-07-15", status: "cancelled", total: 2400, currency: "JPY", items: [{ name: "Merino socks, 3 pairs", quantity: 1, price: 2400 }] },]view OrderHistoryDemo() = <div.demo-stack> <OrderHistory orders={order_history_demo_orders} open={0}/> <OrderHistory orders={[]}/> </div>Source
ui/order_history.qwe
// OrderHistory: past orders, one line each with its number, date, status and// total, opening (on <details>, so with no script) to show what was in it and// the links to the order and its tracking. Prices are written as Price writes// them (this file needs price.qwe).//// <OrderHistory orders={[// { id: "10482", date: "2026-10-20", status: "delivered", total: 113, href: "/orders/10482",// items: [{ name: "Trail runner", quantity: 1, price: 89, image: "/p/1.jpg" }, { name: "Wool beanie", quantity: 1, price: 24 }] },// { id: "10477", date: "2026-10-12", status: "shipped", total: 32.5, tracking_href: "https://...", items: [...] }]}/>//// orders: a list of { id, date, status, total, items, href?, tracking_href?,// currency?, note? }: date an ISO date (written for locale); status one of// "processing", "paid", "shipped", "delivered", "cancelled", "refunded" or// "returned" (status_text in the order says it otherwise); items a list of// { name, quantity?, price?, image?, options? }. The newest first is up to// you. open: the index of the order open to start with (none). currency,// locale, cents as for Price. empty: what to say with no orders ("No orders// yet"). label: what screen readers call the list ("Orders").import ui/price.qwe use price_textimport ui/theme.qwe use ui_clslet order_history_css = css`.ui-orders { display: grid; gap: 10px; min-width: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-orders-item { border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); }.ui-orders-item > summary { display: grid; grid-template-columns: auto 1fr auto auto auto; gap: 12px 16px; align-items: center; padding: 12px 16px; cursor: pointer; list-style: none; border-radius: var(--ui-radius); }.ui-orders-item > summary::-webkit-details-marker { display: none; }.ui-orders-item > summary:hover { background: var(--ui-subtle); }.ui-orders-item > summary:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-orders-chev { width: 8px; height: 8px; border: solid var(--ui-muted); border-width: 0 1.5px 1.5px 0; transform: rotate(-45deg); transition: transform .2s; }.ui-orders-item[open] > summary .ui-orders-chev { transform: rotate(45deg); }.ui-orders-id { font-weight: 600; }.ui-orders-id span { font-weight: 400; color: var(--ui-muted); }.ui-orders-date { color: var(--ui-muted); white-space: nowrap; }.ui-orders-status { display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 9px; border-radius: 999px; font: 500 12px/1 var(--ui-font); white-space: nowrap; --t: var(--ui-muted); --ts: var(--ui-subtle); color: var(--t); background: var(--ts); }.ui-orders-status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--t); }.ui-orders-processing, .ui-orders-paid { --t: var(--ui-warning); --ts: var(--ui-warning-soft); }.ui-orders-shipped { --t: var(--ui-info); --ts: var(--ui-info-soft); }.ui-orders-delivered { --t: var(--ui-success); --ts: var(--ui-success-soft); }.ui-orders-cancelled { --t: var(--ui-danger); --ts: var(--ui-danger-soft); }.ui-orders-total { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-orders-body { padding: 4px 16px 14px; border-top: 1px solid var(--ui-border); }.ui-orders-lines { margin: 0; padding: 0; list-style: none; }.ui-orders-line { display: grid; grid-template-columns: 40px 1fr auto; gap: 12px; align-items: center; padding: 8px 0; }.ui-orders-line + .ui-orders-line { border-top: 1px solid var(--ui-border); }.ui-orders-line img, .ui-orders-ph { display: block; width: 40px; height: 40px; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); object-fit: cover; }.ui-orders-line-text { display: grid; gap: 1px; min-width: 0; }.ui-orders-line-meta { font-size: 12px; color: var(--ui-muted); }.ui-orders-line-price { font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-orders-note { margin: 8px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-orders-links { display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 10px; font-size: 13px; }.ui-orders-links a { color: var(--ui-accent); font-weight: 500; text-decoration: none; }.ui-orders-links a:hover { text-decoration: underline; }.ui-orders-empty { padding: 32px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius); color: var(--ui-muted); text-align: center; }@media (max-width: 600px) { .ui-orders-item > summary { grid-template-columns: auto 1fr auto; } .ui-orders-date, .ui-orders-total { grid-column: 2 / -1; } }@media (prefers-reduced-motion: reduce) { .ui-orders-chev { transition: none; } }`let order_history_words = { processing: "Processing", paid: "Paid", shipped: "Shipped", delivered: "Delivered", cancelled: "Cancelled", refunded: "Refunded", returned: "Returned" }view OrderHistory(orders: list = [], open: int? = null, currency: str = "USD", locale: str? = null, cents: bool = true, empty: str = "No orders yet", label: str = "Orders", class: str? = null) { style global order_history_css len(orders) == 0 ? <div class={ui_cls(["ui-orders-empty", class])} role="status">{empty}</div> : <div class={ui_cls(["ui-orders", class])} role="list" aria-label={label}> {for [i, o] in enumerate(orders) => { let cur = get(o, "currency") ?? currency let items = get(o, "items") ?? [] let count = sum(items | map((it) => get(it, "quantity") ?? 1)) <details.ui-orders-item key={o.id} role="listitem" open={i == open}> <summary> <span.ui-orders-chev aria-hidden="true"></span> <span.ui-orders-id><span>Order </span>#{o.id}</span> <span.ui-orders-date>{date.valid(o.date) ? <time datetime={o.date}>{date.format(o.date, { locale: locale ?? "en-GB", style: "medium" })}</time> : o.date}</span> <span class={`ui-orders-status ui-orders-${o.status}`}>{get(o, "status_text") ?? get(order_history_words, o.status) ?? o.status}</span> <span.ui-orders-total>{price_text(o.total, cur, locale, cents)}<span.ui-sr>, {count} {count == 1 ? "item" : "items"}</span></span> </summary> <div.ui-orders-body> <ul.ui-orders-lines> {for [j, it] in enumerate(items) => <li.ui-orders-line key={j}> {get(it, "image") != null ? <img src={it.image} alt="" loading="lazy"/> : <span.ui-orders-ph aria-hidden="true"></span>} <span.ui-orders-line-text> <span>{it.name}</span> <span.ui-orders-line-meta> {`Qty ${get(it, "quantity") ?? 1}`}{len(get(it, "options") ?? []) > 0 && ` · ${join(it.options, ", ")}`} </span> </span> {get(it, "price") != null && <span.ui-orders-line-price>{price_text(it.price, cur, locale, cents)}</span>} </li>} </ul> {get(o, "note") != null && <p.ui-orders-note>{o.note}</p>} {(get(o, "href") != null || get(o, "tracking_href") != null) && <div.ui-orders-links> {get(o, "href") != null && <a href={o.href}>View order<span.ui-sr> #{o.id}</span></a>} {get(o, "tracking_href") != null && <a href={o.tracking_href}>Track package<span.ui-sr> of order #{o.id}</span></a>} </div>} </div> </details> }} </div>}Related
AddressCardA saved address with a Default badge and Edit and Remove, as submit buttons in a plain form or callbacks.6 views · 7 downloadsDiscountBadge"−20%" or "Save $10" as a small label, in tones, solid, soft or outline, or pinned to an image's corner.7 views · 8 downloadsOrderTrackerOrdered → shipped → out for delivery → delivered, with when each happened, the expected date and the tracking link.5 views · 6 downloadsProductGridProducts as ProductCards in a grid that fits as many across as there is room for, from a list of records.6 views · 5 downloadsStockStatusIn stock, only a few left, out of stock, pre-order or back-order, with a coloured dot and the count.7 views · 5 downloadsCheckListItems shown done or not with ticks and crosses (or empty circles), read-only, with an optional count.5 views · 8 downloads
Thread
Thread 0 posts
On OrderHistory: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.