Orders Table
Free Table React component — OrdersTable. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
E-commerce orders table — sort by date, total, or status. The standard admin dashboard orders view.
Preview
Recent orders
Last 7 days
| Order | Customer | |||
|---|---|---|---|---|
| #3210 | Northwind Studio | Mar 8 | $248.00 | Fulfilled |
| #3209 | Harbor Labs | Mar 8 | $92.00 | Processing |
| #3208 | Elm & Co. | Mar 7 | $156.00 | Fulfilled |
| #3207 | Papertrail | Mar 6 | $64.00 | Cancelled |
| #3206 | Studio Nine | Mar 5 | $312.00 | Fulfilled |
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/table/orders-table.tsxin your project. - 4
Import and render:
Exampleimport { OrdersTable } from "@/components/table/orders-table";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/table/orders-table.tsx
"use client";import {forwardRef,useMemo,useState,type ComponentPropsWithoutRef,} from "react";import { cn } from "@/lib/cn";import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react";type Order = Readonly<{id: string;customer: string;date: string;amount: number;status: "fulfilled" | "processing" | "cancelled";}>;const ORDERS: Order[] = [{id: "#3210",customer: "Northwind Studio",date: "Mar 8",amount: 248,status: "fulfilled",},{id: "#3209",customer: "Harbor Labs",date: "Mar 8",amount: 92,status: "processing",},{id: "#3208",customer: "Elm & Co.",date: "Mar 7",amount: 156,status: "fulfilled",},{id: "#3207",customer: "Papertrail",date: "Mar 6",amount: 64,status: "cancelled",},{id: "#3206",customer: "Studio Nine",date: "Mar 5",amount: 312,status: "fulfilled",},];type SortKey = "date" | "amount" | "status";type SortDir = "asc" | "desc";const STATUS_LABEL: Record<Order["status"], string> = {fulfilled: "Fulfilled",processing: "Processing",cancelled: "Cancelled",};function compareOrders(a: Order, b: Order, key: SortKey, dir: SortDir) {const sign = dir === "asc" ? 1 : -1;if (key === "amount") return (a.amount - b.amount) * sign;if (key === "status") return a.status.localeCompare(b.status) * sign;return a.date.localeCompare(b.date) * sign;}type OrdersTableProps = ComponentPropsWithoutRef<"div">;// E-commerce orders table — sort by date, amount, or status. Common admin dashboard pattern.export const OrdersTable = forwardRef<HTMLDivElement, OrdersTableProps>(function OrdersTable({ className, ...props }, ref) {const [sortKey, setSortKey] = useState<SortKey>("date");const [sortDir, setSortDir] = useState<SortDir>("desc");const sorted = useMemo(() => [...ORDERS].sort((a, b) => compareOrders(a, b, sortKey, sortDir)),[sortKey, sortDir],);function toggleSort(key: SortKey) {if (sortKey === key) {setSortDir((d) => (d === "asc" ? "desc" : "asc"));return;}setSortKey(key);setSortDir("desc");}function SortIcon({ column }: { column: SortKey }) {if (sortKey !== column) {return (<ArrowUpDown className="size-3 text-neutral-300" strokeWidth={2} />);}return sortDir === "asc" ? (<ArrowUp className="size-3 text-neutral-600" strokeWidth={2.5} />) : (<ArrowDown className="size-3 text-neutral-600" strokeWidth={2.5} />);}return (<divref={ref}className={cn("w-full max-w-lg overflow-hidden rounded-xl border border-neutral-200 bg-white",className,)}{...props}><div className="border-b border-neutral-100 px-4 py-3"><p className="text-sm font-semibold text-neutral-900">Recent orders</p><p className="text-xs text-neutral-500">Last 7 days</p></div><div className="overflow-x-auto"><table className="w-full min-w-[300px] border-collapse text-left text-sm"><thead><tr className="text-xs text-neutral-500"><th className="px-4 py-2.5 font-medium">Order</th><th className="hidden px-3 py-2.5 font-medium md:table-cell">Customer</th><th className="px-3 py-2.5 font-medium"><buttontype="button"onClick={() => toggleSort("date")}className="inline-flex items-center gap-1 transition-colors hover:text-neutral-700">Date<SortIcon column="date" /></button></th><th className="px-3 py-2.5 text-right font-medium"><buttontype="button"onClick={() => toggleSort("amount")}className="ml-auto inline-flex items-center gap-1 transition-colors hover:text-neutral-700">Total<SortIcon column="amount" /></button></th><th className="px-4 py-2.5 font-medium"><buttontype="button"onClick={() => toggleSort("status")}className="inline-flex items-center gap-1 transition-colors hover:text-neutral-700">Status<SortIcon column="status" /></button></th></tr></thead><tbody>{sorted.map((order) => (<trkey={order.id}className="border-t border-neutral-100 transition-colors hover:bg-neutral-50/80"><td className="px-4 py-3 font-medium text-neutral-900">{order.id}</td><td className="hidden px-3 py-3 text-neutral-600 md:table-cell">{order.customer}</td><td className="px-3 py-3 text-neutral-500">{order.date}</td><td className="px-3 py-3 text-right text-neutral-900 tabular-nums">${order.amount.toFixed(2)}</td><td className="px-4 py-3 text-xs text-neutral-600">{STATUS_LABEL[order.status]}</td></tr>))}</tbody></table></div></div>);},);OrdersTable.displayName = "OrdersTable";