Order History Page Design: List, Detail and Actions
Order history page design looks like a solved problem: a table with dates, totals and a View link. Then the support inbox fills with "where is my order" and "can I still return this", because the table never answered those questions. This guide walks through what an order history UI needs to handle on its own, with the Order History Page block from Wingo UI, the library we build, as the working example: orders grouped by month, search and a status filter, Track, Buy again and Invoice on every row, and a detail that sits beside the list on desktop and becomes its own screen on a phone.
Why do people open the order history page?
Half of them come to track an open order, and the rest want returns, receipts and old purchases. Baymard Institute's orders overview research (opens in a new tab) (accessed November 6, 2026) lists why users visit the page: 50% to track an open order, 26% to start a return, 26% to review past orders, 19% to edit an ongoing order, 15% to cancel and 8% to download an invoice or receipt. Their 2026 accounts and self-service study (opens in a new tab) (published September 9, 2026) adds two findings that shape the layout: order details hidden in collapsed sections performed as poorly in testing as no details at all, and participants went back to past orders to buy the same items again instead of searching the store.
That gives an ecommerce order history page four jobs, each with a place on screen:
Ready-made order history blocks exist. Tailwind Plus (opens in a new tab) has an order history pages category, and shadcnblocks (opens in a new tab) lists five order history blocks with status tabs, filters, per-item status, a timeline and returns (both checked November 6, 2026). The shadcnblocks descriptions mention no search, no detail beside the list and no separate phone view, so check what you would still build yourself: search, list and detail on one screen, phone navigation and the loading state of each action.
What does a good order history UI look like?
A list you can scan and a detail you can act on. Install the block to follow along:
The list groups orders under month headings (groupBy="month", or "none" for a flat list) and sorts them by placedAt, newest first, whatever order you pass them in. Each row shows the order number, a status badge, the date, the item count, the total and the item photos (three plus a +N count when an order has more than four lines). Its actions follow the status: Track while an order is processing or shipped, Buy again once it is delivered, returned or cancelled, and Invoice on everything except cancelled orders.
Over the list sit a search field and a status filter. Search matches order numbers (with or without the #), product names, variants and SKUs, so "skyline" finds the print and "NW-PR" finds every print. The filter offers only the statuses that exist in the list, so nobody picks Returned and gets nothing. Press / anywhere to jump to search (searchShortcut), and when nothing matches, the empty result offers Clear filters.
$ npx wingo-ui@latest add order-history-pageWhat belongs in an order details page design?
Everything, expanded. Baymard's finding about collapsed sections is the reason the detail shows every part open, in the order people look for things:
- The tracker. Status, arrival window or delivery time, milestones, the courier with a tracking number to copy, and the shipment history. This is the Order Tracker, so the page answers "where is it" without sending people to the carrier's site.
- Items. Photo, title, variant, quantity, unit price and line total.
- Shipping and payment. Shipping and billing addresses ("Same as shipping address" when they match), the payment method ("Visa ending in 4242") and the delivery method, laid out with the Description List.
- Summary. Subtotal, discount with its code, shipping (Free when it is 0), tax and total.
- Help. Return items while the window is open, the closing date once it is not, and Get help.
The return line deserves a note. The same Baymard study found that simply removing the Start Return button after the window closed caused needless uncertainty and wasted participants' time. The block keeps a sentence in its place: "The return window closed on Oct 12, 2026." People read that and stop looking, which is the point.
The tracker also works on its own, for example on the order page your shipping email links to:
$ npx wingo-ui@latest add order-trackerHow should the my orders page work on a phone?
As two screens: first the list, then the order on its own view. On a wide screen the detail opens beside the list, the open row lifts, and you can drag the divider to resize the list between 320 and 560px (listWidth sets the start). Below 768px the rows become cards and a tap pushes the order in as its own view with an Orders back button. Swipe right on the detail or press Escape to go back; the list stays mounted underneath, so it keeps its scroll position.
The layout comes from the Split View, which you can use for any list and detail screen. The 768px breakpoint is the block's own width, so the stacked layout also applies when the page sits in a narrow column next to an account sidebar.
Two details matter on phones. First, a phone always starts on the list, even when an order is selected, because opening a long detail before people chose it hides the list they came for. Second, the status filter opens as a bottom sheet on viewports below 768px instead of a dropdown, so the options sit under the thumb. Keyboard users on desktop get Up and Down to move between orders, Home and End to jump, and Enter to open.
If your orders are wide tables today, our post on responsive tables as mobile cards covers the same switch for other data.
How do I wire the order history page into Next.js?
Load the orders on the server, map them to the block's OrderHistoryOrder shape, and render the block in a client component with three callbacks. Mapping first, because it holds the one pitfall: the block takes amounts in major units (dollars, not cents), and payment providers such as Stripe store cents.
OrderRow and getOrdersForCustomer stand for your own data layer. Leave billingAddress out when it matches the shipping address and the detail says so. The page stays a server component; in Next.js 16, searchParams is a promise:
The client component connects the actions. Each callback can return a promise: the button shows a spinner while it runs and a toast when it is done (the block mounts its own toaster). A thrown error shows its message in a toast, or labels.actionFailed ("Something went wrong. Try again.") when it has none, so answer failures with text a shopper can read.
A few rules for the server side:
- Buy again re-prices. The default toast says "Prices and stock are checked again at checkout." Make that true: add the SKUs to the cart and let the cart look up today's prices, never the old ones from the order.
- Check the return window again. The block offers Return items until
returnByon the client clock (nowoverrides it). Your returns endpoint decides for real. - Pick one invoice path. With
onDownloadInvoice, Invoice shows on every order that was not cancelled, so your endpoint has to answer for all of them. Without it, Invoice is a download link to the order'sinvoiceUrland disappears when the order has none.
height="fill" makes the block take the remaining height of a flex column, which is what you want inside an account layout with its own header. On a standalone page, the default height="screen" makes it exactly the viewport. That is the whole my orders page React side: one server page, one mapping function and one client component.
How do I change the copy and the look?
Through props. Every visible string lives in labels, with English defaults in DEFAULT_ORDER_HISTORY_PAGE_LABELS, and statuses merges one by one, so you can rename one status and keep the rest:
tone colors the tracker's progress and the Buy again button (neutral by default), radius sets panels to 12, 16 or 24px, showMap adds a route illustration to the tracker and showHistory={false} drops the shipment history. Pass loading to show skeleton rows while the orders load, and an empty orders array shows "No orders yet" with a Start shopping link.
What should I build next to it?
The pages that feed it. Orders come from a checkout, and the Checkout Page block ends on a confirmation with Track order, which is where this page should open. Our React checkout page tutorial wires it up, and the Storefront Page template covers the shop in front of both. For the rest of an app's screens, browse the build guides, starting with the SaaS website template guide.
Where do I start?
Open the Order History Page docs: the playground lets you switch the starting status and order, change the tone, radius and list width, and try the loading state. The block, the tracker and the split view are part of Wingo UI Pro, which costs $8 a month, $80 a year or $150 for lifetime access as of November 2026 (pricing). The 14-day refund applies only while no Pro item has been downloaded, so try everything in the playground first.
FAQ
What should an order history page show for each order?
The order number, the date, a status badge, the item count, the total and small photos of the items, plus the actions that fit the status: Track while it is on the way, Buy again once it ended and Invoice unless it was cancelled. People should recognize an order without opening it.
Should order details open on a separate page or beside the list?
Beside the list on wide screens, so people can compare orders without losing their place, and as its own full screen on phones, with a back button and swipe back. A modal works poorly here because the detail is long and people scroll it.
How do I show a return window that has closed?
Keep a line in the order detail that says when the window closed, instead of silently removing the return button. Baymard's 2026 accounts research found that simply removing the Start Return button after the window closed caused needless uncertainty and wasted participants' time.
Does the order history block take prices in cents or dollars?
Dollars. The Wingo UI block takes every amount in major units, so divide the cent amounts that Stripe and most databases store by 100 when you map your rows.
Does search on a my orders page need a server endpoint?
Not when you can load a customer's orders in one response: the Wingo UI block filters on the client by order number, product name, variant and SKU. Accounts with hundreds of orders, such as B2B buyers, need server-side search and paging instead.
- E-commerce
- Account pages
- Mobile UI
- Next.js
- React

