Zubeyr··PagesdetaillayoutOrder Detail
A record detail page: header with status badges and navigation, a customer note, line items, a payment summary and a side panel with contact, address, conversion, tags and timeline.
Live demo ↗Installation
Run the shadcn CLI in a project that already has components.json. Using the @tomei tab? Register the namespace once first.
pnpm dlx shadcn@latest add https://tomei.zubeyr.dev/ui/r/order-detail.json- →Installs
lucide-reactfrom npm. - →Pulls in
sidebar. - →Pulls in
button. - →Pulls in
badge. - →Pulls in
timeline. - →Files land in
components/dashboard/order-detail.
Installing by hand? Add the dependencies, then copy the source code below.
pnpm add lucide-reactUsage
import { OrderDetail } from '@/components/dashboard/order-detail/order-detail'
export default function Demo() {
return <OrderDetail />
}Source code
import {
BarChart3,
ChevronDown,
ChevronUp,
Globe,
Home,
Mail,
MapPin,
MoreHorizontal,
Package,
Pencil,
Plus,
Printer,
ShoppingBag,
Truck,
Users,
} from 'lucide-react'
import { Badge } from '@/registry/dashboard/components/badge/badge'
import { Button } from '@/registry/dashboard/components/button/button'
import { InfoRow, InfoSection } from './info-section'
import { Note } from './note'
import { PageHeader } from './page-header'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarItem,
} from '@/registry/dashboard/components/sidebar/sidebar'
import { SummaryList } from './summary-list'
import { LineItems } from './line-items'
import { Timeline } from '@/registry/dashboard/blocks/timeline/timeline'
import { customer, events, items, tags } from './data'
export function OrderDetail() {
return (
<div className="flex h-svh overflow-hidden bg-sidebar text-foreground">
<Sidebar className="border-r-0 max-lg:hidden">
<SidebarHeader
logo={<span className="size-8 rounded-lg bg-sidebar-primary-foreground" />}
title="Tomei"
subtitle="Store admin"
/>
<SidebarContent>
<SidebarGroup>
<SidebarItem icon={<Home />}>Home</SidebarItem>
<SidebarItem icon={<ShoppingBag />} count={12} active>
Orders
</SidebarItem>
<SidebarItem icon={<Package />}>Products</SidebarItem>
<SidebarItem icon={<Users />}>Customers</SidebarItem>
<SidebarItem icon={<BarChart3 />}>Analytics</SidebarItem>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarItem icon={<Globe />}>Online store</SidebarItem>
</SidebarFooter>
</Sidebar>
<main className="min-w-0 flex-1 overflow-y-auto bg-card lg:my-2 lg:mr-2 lg:rounded-2xl lg:border">
<div className="mx-auto flex max-w-[1120px] flex-col gap-8 p-6">
<PageHeader
backHref="#"
breadcrumb={
<span className="flex items-center gap-1.5">
<ShoppingBag className="size-4" /> Orders
</span>
}
tools={
<>
<Button size="icon" aria-label="Print">
<Printer />
</Button>
<Button size="icon" aria-label="Edit">
<Pencil />
</Button>
<Button size="icon" aria-label="More actions">
<MoreHorizontal />
</Button>
<Button size="icon" aria-label="Previous order">
<ChevronUp />
</Button>
<Button size="icon" aria-label="Next order">
<ChevronDown />
</Button>
</>
}
title="Order #1078"
badges={
<>
<Badge indicator="dot">Paid</Badge>
<Badge tone="warning" indicator="ring">
Unfulfilled
</Badge>
</>
}
subtitle="Oct 29, 2026 at 7:24 PM via Online Store"
/>
<div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_20rem]">
<div className="flex min-w-0 flex-col gap-8">
<Note label="Note from customer">
Please delay shipping by two days, I'm away and can't collect the package.
Thanks!
</Note>
<LineItems
items={items}
status={
<Badge tone="warning" indicator="ring">
Unfulfilled
</Badge>
}
actions={
<>
<Button variant="ghost" size="icon" aria-label="More fulfillment actions">
<MoreHorizontal />
</Button>
<Button>Mark as fulfilled</Button>
<Button variant="primary">Create shipping label</Button>
</>
}
meta={
<>
<span className="flex items-center gap-2">
<Truck /> Express
</span>
<span className="flex items-center gap-2">
<MapPin /> Main warehouse
</span>
</>
}
/>
<section className="flex flex-col gap-3">
<h2 className="text-base font-semibold">Payment</h2>
<SummaryList
rows={[
{ label: 'Subtotal', detail: '4 items', value: '$397.50' },
{ label: 'Discount', detail: 'WELCOME10', value: '−$30.00' },
{ label: 'Shipping', detail: 'Express', value: '$4.99' },
]}
total={{ label: 'Total', value: '$372.49' }}
footer={[
{ label: 'Store credit', value: '$50.00' },
{ label: 'Visa •••• 3840', value: '$322.49' },
]}
/>
</section>
</div>
<aside className="flex flex-col">
<InfoSection>
<h2 className="text-lg font-semibold">{customer.name}</h2>
<InfoRow icon={<Mail />}>{customer.email}</InfoRow>
<InfoRow icon={<ShoppingBag />}>{customer.orders} orders</InfoRow>
</InfoSection>
<InfoSection title="Shipping address">
<address className="text-sm leading-6 not-italic">
{customer.address.map((line) => (
<span key={line} className="block">
{line}
</span>
))}
</address>
</InfoSection>
<InfoSection title="Conversion summary">
<InfoRow icon={<ShoppingBag />}>{customer.orders}th order</InfoRow>
<InfoRow icon={<Globe />}>Source: direct</InfoRow>
</InfoSection>
<InfoSection
title="Tags"
action={
<Button variant="ghost" size="icon-sm" aria-label="Add tag">
<Plus />
</Button>
}
>
<div className="flex flex-wrap gap-1.5">
{tags.map((tag) => (
<Badge key={tag}>{tag}</Badge>
))}
</div>
</InfoSection>
<InfoSection title="Timeline">
<Timeline events={events} />
</InfoSection>
</aside>
</div>
</div>
</main>
</div>
)
}import type { TimelineEvent } from '@/registry/dashboard/blocks/timeline/timeline'
import type { LineItem } from './line-items'
const swatch = (color: string) => <span className="size-full" style={{ background: color }} />
export const items: LineItem[] = [
{
id: '1',
name: 'Canvas Sneakers',
variant: '10.5 US',
price: 235,
quantity: 1,
image: swatch('#d6cfc4'),
},
{
id: '2',
name: 'Button-down Shirt',
variant: 'Blue / XL',
price: 65,
quantity: 2,
image: swatch('#3b4a6b'),
},
{
id: '3',
name: 'Knit Beanie',
variant: 'One size',
price: 32.5,
quantity: 1,
image: swatch('#6b7a4f'),
},
]
export const customer = {
name: 'Jordan Ellis',
email: 'jordan@example.com',
orders: 4,
address: ['Jordan Ellis', '184 Kent Ave', 'Brooklyn, NY', 'United States'],
}
export const tags = ['VIP', 'Wholesale']
export const events: TimelineEvent[] = [
{ id: '1', content: 'Order confirmation email was sent to Jordan Ellis.', time: '1:36 PM' },
{ id: '2', content: 'A $322.49 payment was processed on Visa •••• 3840.', time: '1:36 PM' },
{ id: '3', content: 'Jordan Ellis placed this order on the Online Store.', time: '1:35 PM' },
]import { cn } from '@/lib/utils'
type InfoSectionProps = {
title?: React.ReactNode
action?: React.ReactNode
className?: string
children?: React.ReactNode
}
export function InfoSection({ title, action, className, children }: InfoSectionProps) {
return (
<section
className={cn('flex flex-col gap-3 border-b py-5 first:pt-0 last:border-0', className)}
>
{(title || action) && (
<div className="flex items-center justify-between gap-2">
{title && <h2 className="text-sm text-muted-foreground">{title}</h2>}
{action}
</div>
)}
{children}
</section>
)
}
export function InfoRow({
icon,
className,
children,
}: {
icon?: React.ReactNode
className?: string
children: React.ReactNode
}) {
return (
<p
className={cn(
'flex min-w-0 items-center gap-2.5 text-sm [&_svg]:size-4 [&_svg]:shrink-0',
className,
)}
>
{icon && <span className="flex text-muted-foreground">{icon}</span>}
<span className="min-w-0 truncate">{children}</span>
</p>
)
}import { cn } from '@/lib/utils'
export type LineItem = {
id: string
name: React.ReactNode
image?: React.ReactNode
variant?: React.ReactNode
price: number
quantity: number
}
type LineItemsProps = {
items: LineItem[]
status?: React.ReactNode
actions?: React.ReactNode
meta?: React.ReactNode
formatOptions?: Intl.NumberFormatOptions
className?: string
}
export function LineItems({
items,
status,
actions,
meta,
formatOptions = { style: 'currency', currency: 'USD' },
className,
}: LineItemsProps) {
const money = new Intl.NumberFormat('en-US', formatOptions)
return (
<section className={cn('flex flex-col gap-3', className)}>
{(status || actions) && (
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2 text-base font-semibold">{status}</div>
{actions && <div className="flex items-center gap-2">{actions}</div>}
</div>
)}
<div className="rounded-xl bg-card shadow-(--shadow-card)">
{meta && (
<div className="flex items-center justify-between gap-3 border-b px-4 py-3 text-sm text-muted-foreground [&_svg]:size-4">
{meta}
</div>
)}
<ul className="divide-y px-4">
{items.map((item) => (
<li
key={item.id}
className="grid grid-cols-[auto_1fr_auto_auto] items-center gap-4 py-3"
>
<span className="flex size-12 items-center justify-center overflow-hidden rounded-lg bg-muted">
{item.image}
</span>
<span className="flex min-w-0 flex-col items-start gap-1.5">
<span className="truncate text-sm font-medium">{item.name}</span>
{item.variant && (
<span className="rounded-md bg-muted px-1.5 py-0.5 text-xs">{item.variant}</span>
)}
</span>
<span className="flex items-center gap-1.5 text-sm text-muted-foreground tabular-nums max-sm:hidden">
{money.format(item.price)} ×
<span className="rounded bg-muted px-1.5 text-xs text-foreground">
{item.quantity}
</span>
</span>
<span className="w-20 text-right text-sm tabular-nums">
{money.format(item.price * item.quantity)}
</span>
</li>
))}
</ul>
</div>
</section>
)
}import { cn } from '@/lib/utils'
const tones = {
yellow: 'bg-yellow-200/80 dark:bg-yellow-400/25',
blue: 'bg-sky-200/80 dark:bg-sky-400/25',
pink: 'bg-pink-200/80 dark:bg-pink-400/25',
}
type NoteProps = {
label?: React.ReactNode
tone?: keyof typeof tones
className?: string
children: React.ReactNode
}
export function Note({ label, tone = 'yellow', className, children }: NoteProps) {
return (
<figure className={cn('flex flex-col gap-1.5 border-l pl-3', className)}>
{label && <figcaption className="text-xs text-muted-foreground">{label}</figcaption>}
<blockquote className="text-sm leading-7">
<span className={cn('box-decoration-clone px-1.5 py-1', tones[tone])}>{children}</span>
</blockquote>
</figure>
)
}import { cn } from '@/lib/utils'
type PageHeaderProps = {
title: React.ReactNode
badges?: React.ReactNode
subtitle?: React.ReactNode
backHref?: string
breadcrumb?: React.ReactNode
tools?: React.ReactNode
actions?: React.ReactNode
className?: string
}
export function PageHeader({
title,
badges,
subtitle,
backHref,
breadcrumb,
tools,
actions,
className,
}: PageHeaderProps) {
return (
<header className={cn('flex flex-col gap-6', className)}>
{(backHref || breadcrumb || tools) && (
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{backHref && (
<a
href={backHref}
aria-label="Back"
className="flex size-8 items-center justify-center rounded-full bg-card text-foreground shadow-(--shadow-button) hover:bg-muted"
>
<svg viewBox="0 0 16 16" fill="none" aria-hidden className="size-4">
<path
d="M10 3 5 8l5 5"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</a>
)}
{breadcrumb}
</div>
{tools && <div className="flex items-center gap-1.5">{tools}</div>}
</div>
)}
<div className="flex flex-wrap items-end justify-between gap-4">
<div className="flex min-w-0 flex-col gap-2">
<div className="flex flex-wrap items-center gap-2">
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
{badges}
</div>
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
</div>
{actions && <div className="flex flex-wrap items-center gap-2">{actions}</div>}
</div>
</header>
)
}import { cn } from '@/lib/utils'
export type SummaryRow = {
label: React.ReactNode
detail?: React.ReactNode
value: React.ReactNode
}
type SummaryListProps = {
rows: SummaryRow[]
total?: { label: React.ReactNode; value: React.ReactNode }
footer?: SummaryRow[]
className?: string
}
function Row({ label, detail, value, strong }: SummaryRow & { strong?: boolean }) {
return (
<div
className={cn(
'grid grid-cols-[1fr_auto_6rem] items-center gap-4 py-2 text-sm',
strong && 'font-semibold',
)}
>
<span>{label}</span>
<span className="text-right text-muted-foreground">{detail}</span>
<span className="text-right tabular-nums">{value}</span>
</div>
)
}
export function SummaryList({ rows, total, footer, className }: SummaryListProps) {
return (
<div className={cn('rounded-xl bg-muted', className)}>
<div className="rounded-xl bg-card px-4 py-2 shadow-(--shadow-card)">
{rows.map((row, i) => (
<Row key={i} {...row} />
))}
{total && (
<div className="mt-1 border-t pt-1">
<Row label={total.label} value={total.value} strong />
</div>
)}
</div>
{footer?.length ? (
<div className="px-4 py-2">
{footer.map((row, i) => (
<Row key={i} {...row} />
))}
</div>
) : null}
</div>
)
}